Chapter 11. Printing with Flex

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

Bill Gates

In general, the process of printing from web applications works a little differently compared to printing from the desktop. Web applications have good reason for not allowing direct access to a user’s printer: malicious websites could immediately start printing their fliers on your home or corporate printer, offering you anything from pizza delivery to adult entertainment. That’s why you can’t write a program in JavaScript that would automatically detect all available printers and send them print jobs. That’s why the user is forced to manually select the printer via the web browser’s pop-up dialog window.

Existing Flash Player bugs add more issues for Flex developers; for example, the Print dialog might not report all features of the available printer, and setting such parameters as tray selection or paper size might not be possible. To put it simply, you may not have complete control over the user’s printer from an application running in Flash Player. You may need to adjust your reports to standard printer settings.

Note

Adobe had a product called FlashPaper that tried to mitigate these limitations by adding ActionScript 2 objects to a special control with complete access to the printer. In 2008, however, Adobe discontinued FlashPaper, apparently promoting printing PDF documents using Acrobat instead.

The process of printing from Flash Player consists of starting a single-threaded print job and adding dynamically created pages to it (i.e., the data that comes from a database). Unfortunately, Flash Player’s virtual machine AVM2 ActionScript timeout is 15 seconds. Accordingly, for both Flex and AIR, the interval between the following commands shouldn’t be more than 15 seconds:

  • PrintJob.start() and the first PrintJob.addPage()

  • PrintJob.addPage() and the next PrintJob.addPage()

  • PrintJob.addPage() and PrintJob.send()

If, at each of these commands, printing the specified page always completed in 15 seconds or less, your application will be able to print a multipage document, although somewhat slowly. If any of the intervals spans more than 15 seconds, however, your print job will receive a runtime exception, which turns direct printing from Flash Player into an unpleasant experience, if application developers don’t handle exceptions properly. Plus, if the internal thread that started the print job failed, it may be automatically closed and unable to be recovered properly.

Note

You can read more about handling printing errors in the Adobe document “Flash Player and Air tasks and system printing”.

You may think that setTimeout() can help break the 15-second barrier for printing, but it can’t. Printing has to be handled by the same internal AVM2 thread (apparently a bug), and with setTimeout(), you are in fact spawning a new one. The issue with printing long documents is demonstrated in Example 11-1. The PrintJob starts and the method finishPrinting() is called in the same thread and works fine. If you instead comment out the call to finishPrinting() and uncomment the method setTimeout(), this printing job will fail: the addPage() will throw an exception, because it runs in a thread different than PrintJob.

Imagine that a timeout was initiated not by calling the function setTimeout(), but rather by Flash Player during printing of a multipage document because one of the addPage() calls took longer than 15 seconds. In this case, addPage() would be called on a different internal thread than PrintJob.start() and the addPage() operation would fail, even though Flash Player should’ve known how to process a such situation properly.

Example 11-1. PrintTimeout.mxml—an illustration of printing failure

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical">
   <mx:Button label="Print Me" click="printMe()"/>
   <mx:Script>
      <![CDATA[

         private function printMe() :void {
            var pj:PrintJob = new PrintJob();
            pj.start();

   //           setTimeout(function() :void { finishPrinting(pj);}, 1);

                   finishPrinting(pj);
         }

         private function finishPrinting(pj:PrintJob): void {
            pj.addPage(this);
            pj.send();
         }
      ]]>
   </mx:Script>
</mx:WindowedApplication>

Example 11-1 just prints itself, addPage(this), but if it had to print, say, a slow-rendered DataGrid with a couple of thousand rows, the chances are high that such a program would time out before the printing job was finished.

There is a bigger problem than the technical restrictions mentioned so far, and it is in the very approach to printing via the PrintJob API. The process of programming reports in ActionScript comes down to creating snapshots of components displayed on the users’ monitors and sending them to the printer. Because screen resolution differs from printer resolution, however, application developers pursing this method need to create separate layouts just for printing, which is time-consuming and challenging.

That’s why you should consider creating and printing your reports as PDF files. Besides, it neatly reinforces this book’s philosophy: minimize the amount of code that business application developers have to write. In this chapter, you’ll learn how to create XDP-enabled Flex components that will allow application developers to generate PDF documents on the client side with minimal coding.

PDF Generation on the Server

PDF generation is supported by Adobe LiveCycle and LCDS, as well as other server-side products. Suppose that you have a Flex or AIR window with a number of UI controls, and you want to create a PDF out of it. One option is to create a snapshot of the Flex component or container using the class mx.graphics.ImageSnapshot and its function captureImage(), which can scale the image to a specific resolution and encode it into a specified image format. You can send an instance of this class via RemoteObject to the server with LCDS installed. LCDS then creates a PDF document (or merges it with a PDF form) and includes the new image received from the client.

The problem with this approach is that the resulting PDF will not be searchable. For instance, if a Flex Panel has text fields, you won’t be able to find the text of these fields in Acrobat Reader if the Panel is embedded as a bitmap.

Such PDFs have limitations on resolution as well (to create a PDF with resolution 300 dpi, you’d need to create a multimegabyte image). Also, printed materials often use different CSS and metrics from the screen ones. You don’t want to print, say, a background gradient that looks fine on the monitor, but bad on paper.

To embed forms into PDF documents, Adobe uses the XDP format. If you purchase an LCDS license, you’ll have the option to use it. You can design forms in Acrobat Designer and export the data from your Flex view, and LCDS will merge the data and the form on the server. On the Java side, LCDS adds several JARs in the lib directory of your web application, which makes the XFAHelper Java class available for your server-side PDF generation.

After generating the PDF, the server-side program can be:

  • Placed as a ByteArray in HTTPSession object

  • Saved as a file on the server for future use

  • Streamed back to the client marked as a MIME type application/pdf

  • Saved in a DBMS field as a binary large object (BLOB)

Depending on the business requirements, the server-side PDF generation might not be feasible. You might have just disconnected the AIR application, or the server software may not have any of the technologies supporting PDF creation installed. If the Flex UI is truly dynamic, that might change the number of displayed components based on some business criteria; developing an additional UI in Acrobat Designer just for printing can in these ways become either impossible or time-consuming. The LCDS Developer Guide describes this process in the document called “Using the PDF Generation Feature”.

Note

Adobe has published an article describing the process of creating PDF documents using templates.

In general, for server-side PDF generation from Adobe Flex applications, you have to do the following:

  • Use Adobe LiveCycle Designer ES, which provides tools for creating interactive forms and personalized documents (see http://www.adobe.com/products/livecycle/designer/). This software comes with Acrobat Professional or can be purchased separately, and is well documented, but it requires someone to create the XDP form and the data model and establish a process of synchronizing the Flex application with the server-side LiveCycle.

  • Initiate the server-side PDF generation from your Flex application seamlessly.

Although this process provides guaranteed quality and predictable results, it also requires the double effort of developing XDP forms for printing and Flex forms for displaying. Besides, LiveCycle Designer is another piece of software that application developers in your organization may not be familiar with.

LCDS generation with merging data and forms produces good printing quality with LCDS. The Flex client sends data as XML to the server along with the name of the form file (template) to be used for merging, as shown in Example 11-2. In this case, the LCDS layer just needs to process it with the XDPXFAHelper class and return it as a PDF stream to the browser for displaying.

Note

Only commercial licenses of LCDS support PDF generation.

The ActionScript class FormRenderer sends generated XDP to the server and opens a web browser’s window to display the PDF when it arrives from the server.

Example 11-2. Class FormRenderer.as

import flash.net.*;
import flash.utils.ByteArray;

public class FormRenderer {
 public static function openPdf(xdp:String, target:String="_blank"):void{
   var req:URLRequest = new URLRequest("/createPDF.jsp");
   req.method = URLRequestMethod.POST;

   var ba :ByteArray = new ByteArray();;
   ba.writeMultiByte(xdp, "iso-8859-1");
   ba.compress();
   ba.position = 0;
   req.data = ba;
   navigateToURL(req, target);
 }
}

You also need an XDP file with the data and presentation. If you don’t have LiveCycle Designer, you can make the XDP file programmatically, ensuring that it matches the printer’s paper size and corporate stationery. XDP documents are XML objects, which are easily processed in Flex using E4X syntax, for example:

  1. Declare a variable of type XML, and initialize it with the required XDP template deployed on the server. A fragment of the XDP template may look like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <?xfa generator="AdobeLiveCycleDesigner_V8.0" APIVersion="2.5.6290.0"?>
     <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2007-01-25T10:40:38Z"
      uuid="784f469b-2fd0-4555-a905-6a2d173d0ee1">
    
      <template xmlns="http://www.xfa.org/schema/xfa-template/2.5/">
       <subform name="form1" layout="tb" restoreState="auto" locale="en_US">
        <pageSet>
         <pageArea name="Page1" id="Page1">
          <contentArea x="0.25in" y="0.25in" w="8in" h="10.5in"/>
          <medium stock="letter" short="8.5in" long="11in"/>
          <?templateDesigner expand 1?></pageArea>
          <?templateDesigner expand 1?></pageSet>
    
          <subform w="8in" h="10.5in" name="YourPageAttachedHere"/>
          <proto/>
          <desc>
            <text name="version">8.0.1291.1.339988.308172</text>
          </desc>
       </subform>
      </template>
  2. Select a Flex UI container or component that you are going to print (a Panel, a DataGrid, and so on).

  3. Query the object from step 2, get its XDP attributes and children, and create the XML preparing this object for printing. Attach the XML to the template as a page.

Because original Flex components don’t know how to represent themselves in the XDP format, you’ll need to teach them to do so. This becomes the next task in enhancing Flex components.

For example, each UI component can implement some interface (e.g., IXdpObject with the only getter, xmlContent()) that allows it to return its own XDP content or, in the case of containers, to traverse the list of its child components for their XDP content. For example, a new panel component (PanelXdp) may have the following structure:

public class PanelXdp extends Panel implements IXdpObject{
    public function get xmlContent():Object{
       // The code to return representation of the panel
       // in the XDP format goes here
    }
}

Repeat the process of attaching XML to the XDP template using E4X until all print pages are ready. This method of printing from Flex requires less effort for reporting and creation of dynamic layouts. It might also provide better printing quality and searchability within the printed document.

Example 11-3 is the server-side part written as a Java ServerSide Page. It uncompresses the XDP stream received from the client, creates the PDF using XDPXFAHelper, turns it into an array of bytes, and sends it back to the client as the MIME type "application/pdf".

Example 11-3. Render.jsp

<%@ page language="java"
   import="java.io.*,
   java.util.*,
   javax.xml.parsers.*,
   org.w3c.dom.Document,
   flex.messaging.*,
   flex.acrobat.pdf.XDPXFAHelper,
   flex.messaging.util.UUIDUtils,
   org.w3c.dom.Document
   "
%><%!
private static void _log(Object o){
   System.out.println(""+o);
}
private String getParam(HttpServletRequest request, String name, String defVal)
throws Exception{
       String val = request.getParameter(name);
     return (val!=null && val.length()>0)?val:defVal;
}
private String getParam(HttpServletRequest request, String name) throws Exception{
   return getParam(request, name, null);
}
private void processRenderRequest(HttpServletRequest request,
                        HttpServletResponse response) throws Exception{

   String data      = getParam(request, "document");
   String template = getParam(request, "template"); // Security hole, check path
   _log("template="+template);
   _log("data="+data);
   template = FlexContext.getServletContext().getRealPath(template);
   _log("template real="+template);

       // You must have LCDS license to use XDPXFAHelper
   XDPXFAHelper helper = new XDPXFAHelper();
   helper.open(template);
   // Import XFA dataset
   if( data!=null ){
      _log("data.length="+data.length());
      ByteArrayInputStream bais = new
                        ByteArrayInputStream(data.getBytes("UTF-8"));
      DocumentBuilderFactory builderFactory =
                                    DocumentBuilderFactory.newInstance();
      DocumentBuilder builder =
                                    builderFactory.newDocumentBuilder();
      Document dataset = builder.parse(bais);
      helper.importDataset(dataset);
   } else
      _log("data="+null);

   byte[] content = helper.saveToByteArray();
   _log("content="+content);
   helper.close();
   ServletOutputStream out3 = response.getOutputStream();
   response.setContentType("application/pdf");
   response.setContentLength(content.length);
   out3.write(content);
}
%><%
_log("");
_log("--------------------------------------------");
_log("render.jsp");
processRenderRequest(request, response);
%>

Note

The WebORB PDF Generator from Midnight Coders allows you to either create XML printing templates on the server or generate them in Flex clients. To use this solution, you have to install the WebORB Server. For more details, visit http://www.themidnightcoders.com/products/pdf-generator/overview.html.

Now we’ll take a look at how to generate a PDF on the Flex side.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.46.229