HEAD PREVIOUS

Chapter 12
Wizards, CSV Export, and PDFs

Remember our zero-code contact manager? In this chapter we're going to actually add code to that application. Many desktop applications today provide these software assistants, or wizards, to help newbie users through a task.
For example, when we created a contact person in our contact manager, we did not resort to an assistant. We just right-clicked New on Person and typed away. This works just fine. But maybe we would also like to break the process of entering contact information into these steps:
  1. Enter person's name
  2. Enter person's physical address
  3. Enter remaining contact information (phone numbers, email address)
  4. Finish
Having more than one way to do something can sometimes help. Each method can complement the other. Let's see what kind of support JMatter provides for adding assistants to our user interface.

12.1  Subclassing Person

When we wrote this application, we used pre-written classes. One of them was com.u2d.type.composite.Person. We want to enhance person with an additional behaviour: we want to add a command to launch our wizard. It should be a class command, not an instance command.
So we need to subclass Person. Let's create a package com.u2d.contactmgr. In it we're going to create a new class: PersonContact like this:
package com.u2d.contactmgr;
import com.u2d.type.composite.Person;
import com.u2d.wizard.details.Wizard;
import com.u2d.element.CommandInfo;
public class PersonContact extends Person
{
   @Cmd
   public static Wizard NewPersonWizard(CommandInfo cmdInfo)
   {
      return new Wizard(new NewPersonWizard());
   }
}
Pretty simple. We're basically exposing a command that will return a Wizard object. JMatter knows what to do with these objects, how to display them, allow the end user to navigate through them, etc.. All you need to do is provide the steps.

12.2  Writing the Wizard

JMatter models a wizard as a container for a series of steps. JMatter defines several types of steps. There are basic (or atomic) steps, conditional steps, and composite steps. We need to write a class that is essentially a composite step: one that defines the set of steps that makes up our wizard.
Here is the first part of the implementation for our NewPersonWizard class:
package com.u2d.contactmgr;
import com.u2d.wizard.details.*;
import com.u2d.type.composite.*;
import com.u2d.model.ComplexType;
import com.u2d.view.swing.FormView;
import javax.swing.*;
public class NewPersonWizard extends CompositeStep
{
   private Name _name;
   private USAddress _address;
   private Contact _contact;
   public NewPersonWizard()
   {
      super("New Person Wizard");
      createObjects();
      setupSteps();
   }
   private void createObjects()
   {
      _name = (Name) ComplexType.forClass(Name.class).instance();
      _address = (USAddress) ComplexType.forClass(USAddress.class).instance();
      _contact = (Contact) ComplexType.forClass(Contact.class).instance();
   }
   private void setupSteps()
   {
      NameStep nameStep = new NameStep();
      AddressStep addrStep = new AddressStep();
      ContactStep contactStep = new ContactStep();
      addStep(nameStep);
      addStep(addrStep);
      addStep(contactStep);
      addStep(new CommitWizardStep());
      ready();
   }
}
Let's analyze this code. We defined a class that extends CompositeStep. Recall that our wizard is essentially four steps: specify a name, an address, contact info, and finish. So we define variables that will hold each of these three pieces of information.
Next, in the constructor, we set the title for our wizard with the call to the super type's constructor. We proceed to instantiate our three objects and then to configure them. The convention for setting up the steps is pretty easy:
  1. create each step
  2. add the steps in the proper order
  3. call the ready() method
Now all that's left to do is define three basic steps. I've decided to do this using inner classes, as follows:
class NameStep extends BasicStep
{
   public String title() { return "Name Information"; }
   public String description() { return "Enter Person's Name"; }
   public JComponent getView()
   {
      return new FormView(_name, false, false);
   }
}
class AddressStep extends BasicStep
{
   public String title() { return "Address Information"; }
   public String description() { return "Enter Person's Physical Address"; }
   public JComponent getView()
   {
      return new FormView(_address, false, false);
   }
}
class ContactStep extends BasicStep
{
   public String title() { return "Person's Contact Information"; }
   public String description()
   {
      return "Please specify person's contact information";
   }
   public JComponent getView()
   {
      return new FormView(_contact, false, false);
   }
}
We see here that the implementation is trivial. We provide a title, description, and a view for each of our steps. Nothing to it!
It is worth noting that the FormView class is taken from JMatter's own Swing-based view mechanism. By reusing this class, we automate the chore of having to write the panels that make up our wizard's user interface. Not only do we automate the construction of each panel, but also the binding of the view to its model object, as well as model object validation. For example, entering a zip code with an invalid format in the AddressStep will automatically be flagged, giving the user a chance to revise and fix the data entry (see the screenshots that follow this section) before being able to proceed to the next step in the wizard.
In our last step, the commit step, we sew everything together and save our new contact person:
class CommitWizardStep extends CommitStep
{
   public void commit()
   {
      PersonContact pc = new PersonContact();
      pc.getName().setValue(_name);
      pc.getContact().setValue(_contact);
      pc.getContact().getAddress().setValue(_address);
      pc.save();
   }
   public JComponent getView()
   {
      return new JLabel(description());
   }
   public String title() { return "Final Step"; }
   public String description()
   {
      return "We're almost done; Person record will be " +
             "committed after clicking 'Next'";
   }
}
Besides providing the basic step information: title, description, and view, we also need to fill in the commit() method, which is quite straightforward:
  1. we create a new PersonContact instance
  2. we set the name, contact, and address values
  3. finally, we persist our instance
JMatter does the rest! Let's take our app for a little spin.

12.3  Running the Application

Let's run our app:
$ ant run
The next five figures are screen shots for the various steps in our simple wizard. Note how the name, description, and view for each step are automatically placed in their respective places on the wizard's window.
figures/Wizard-Step0.png
figures/Wizard-Step1.png
figures/Wizard-Step2.png
figures/Wizard-Step3.png
figures/Wizard-Step4.png
This coverage of wizards was meant to be an introduction to the topic. JMatter's wizard framework is complete from the point of view that one can produce wizards of any degree of complexity by combining basic steps, composite steps, and conditional steps in various ways.

12.4  CSV Export

If we like, we can also export our contact list to a CSV file. This feature is built-in to all JMatter applications. Simply browse your contact listing and right-click Export to CSV. You will be prompted for a location to save the file. Below is a screenshot of a small CSV export opened in the spreadsheet application Gnumeric.
figures/CSVExport.png
Figure 12.1: CSV Export Viewed in Gnumeric
Note that this feature could be improved. At the moment the CSV Export command simply exports the table model exposed by the listing. However, in this case, it would be nice if the address fields were flattened as properties for each contact. It'd be even nicer if there a CSV export wizard walked you through the process, allowing you to select which fields to export, which to skip, and in what order to serialize them out to file. We hope to extend this CSV export feature in a future version of JMatter.

12.5  PDFs

12.5.1  JFreeReport Integration

JFreeReport is an open source Java API for reporting; its home page is http://www.jfree.org/jfreereport/.
This API has been around for a number of years. It defines an xml vocabulary for laying out reports. It's somewhat difficult to describe in one or two sentences what JFreeReport is all about. Some of its characteristics are reminiscent of templating technologies where information from our applications can be merged with a the xml report specification to produce the final report. The report specification defines various bands such as report headers and footers, page headers and footers, and the actual report items themselves.
JFreeReport also provides means for you to automatically display a print preview of your report from inside your Swing application. From that dialog, one also has the ability to produce the report in a number of formats, including PDF, print, and Microsoft Excel.
The JMatter framework attempts to make the job of producing reports with JFreeReport easier. The JFreeReport libraries are already bundled with JMatter. Producing a PDF report from JMatter via JFreeReport is a relatively easy task. The task of writing the report specification using JFreeReport's XML specification however remains unchanged.
The authors of this framework have used JFreeReport to produce completed medical forms, merging information in a medical system with a template defining the layout of the form[s] to be completed.

12.5.1.1  The basics

JFreeReport provides two ways in which data can be passed in to its xml report specification:
  1. the implementation of a TableModel interface, which provides the majority of the tabular data to be included in a report, and
  2. a simple properties file with key-value pairs for passing any kind of information to include in the header or footer sections of the report
JMatter defines the following interface:
public interface Reportable
{
   public String reportName();
   public Properties properties();
   public TableModel tableModel();
}
The latter two methods provide the data to bind to the xml report specification. The first method provides the path to the report's xml specification. JMatter basically follows the convention that these xml files be placed alongside source code. The xml files are then loaded into a Java application as a resource from the classpath.
If in a command method, you return a Reportable instance, JMatter will take it from there and use JFreeReport to produce a corresponding PDF file and open it using a PDF reader application.

12.5.1.2  A Simple Example

Let's build a very simple report for our ContactManager application. The point of this section is not to provide documentation for JFreeReport. Rather, it's only to illustrate how the integration works.
So here's a simple mechanism to expose the production of a report that will include all the contacts we have in our system:
@Cmd
public static Reportable Report(CommandInfo cmdInfo)
{
   return new Reportable()
   {
      public String reportName()
      {
         return "com/u2d/contactmgr/Basic.xml";
      }
      public Properties properties()
      {
         return new Properties();
      }
      public TableModel tableModel()
      {
         return ComplexType.forClass(PersonContact.class).
                                          list().tableModel();
      }
   };
}
So basically what I'm doing here is exposing a static command using the JMatter conventions. This command returns a Reportable implementation that in this case passes data only via a tablemodel. I'm basically returning the default table model that JMatter exposes on lists of types.
Here is a very basic sample JFreeReport XML specification:
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE report PUBLIC
      "-//JFreeReport//DTD report definition//EN//simple/version 0.8.5"
      "http://jfreereport.sourceforge.net/report-085.dtd">
<report name="Basic Persons Listing" 
   orientation="portrait" pageformat="LETTER" 
   topmargin="36" bottommargin="36" 
   leftmargin="36" rightmargin="36">
  <configuration>
     <property name="org.jfree.report.modules.gui.base.PreferredWidth">640</property>
     <property name="org.jfree.report.modules.gui.base.PreferredHeight">480</property>
  </configuration>
  <pageheader height="200" fontname="sansserif" fontsize="10" fsbold="true">
    <label x="0" y="0" 
           width="100%" height="12" 
           fonsize="12" fsbold="true"
           alignment="center"><![CDATA[Person Contacts Listing]]></label>
    <line x1="170" y1="50" x2="334" y2="50" weight="0.75" />
  </pageheader>
  <items height="18"
         fontname="sansserif" fontstyle="plain" fontsize="10" fsbold="false"
         vertical-alignment="middle">
    <string-field x="10" y="0" width="250" height="12" alignment="left" 
       fieldname="Person Contacts" />
    <string-field x="270" y="0" width="100" height="12" alignment="left" 
       fieldname="Name" />
    <string-field x="380" y="0" width="250" height="12" alignment="left" 
       fieldname="Contact" />
  </items>
</report>
The essential aspects of this specification are located in the items band, where we see that various string-based fields are specified. JFreeReport allows you to insert images, other data types, to control the font size, style, and placement of the information. JFreeReport also provides a number of built-in functions that can be invoked to calculate sums, for example. Developers can also write and plug in their own custom functions.
The result of invoking this command from the JMatter user interface is the production of the report PDF, as shown below.
figures/JFree-1.png
Figure 12.2: PDF produced with JFreeReport

12.5.2  JasperReports

JasperReports is yet another, even more popular, open, reporting solution for Java. It is a well-supported project. One appealing feature of this project is its companion project iReports, a mature graphical report designer, which does away with the tedium of writing JasperReport's analog to the the xml report specification we just saw in the last section.
Another attractive feature of JasperReports is its support and integration with Hibernate. This makes JasperReports a perfect mate for JMatter. We recently extended the Sympster demo application with an example use of JasperReports for producing a symposium schedule. Simply run Sympster, create a symposium and a number of associated Sessions, and then invoke the Symposium command Report Schedule.
JMatter now bundled the JasperReport libraries with its distribution for your convenience. We also highly recommend you support the JasperReports project by buying a copy of their documentation [6] in print.

12.5.3  Launching PDFs and other Files

The solution that appears to work well for displaying PDFs is to launch the desktop's default PDF viewer, rather than attempt to embed a PDF viewer component within one's application.
The Java platform has such a desktop integration feature in Java SE v6. The state of affairs today is such that the MacOSX platform still lags other platforms and does not yet support this version of Java. Developers often cannot afford to ignore this platform, since the very raison d'être of the Java platform is platform independence.
JMatter has a generic solution to this general problem of "launching" a given file, with the result being the launching of the desktop's default-designated reader for the document in question, combined with the action of opening the file in question within that viewer. This solution will work with earlier versions of Java (e.g. Java 5), and can be used to open PDF files, and other documents (.doc, .txt, .xls, .csv, etc..) in a platform-independent manner.
Here's example code that uses a simple strategy for launching a dynamically generated pdf file:
File reportFile = File.createTempFile("report", ".pdf");
reportFile.deleteOnExit();
// write to the file..
com.u2d.utils.Launcher.openFile(reportFile);
In the above example we use Java's API to create a temporary file in a platform-independent manner and specify that the file should be deleted after the end-user quits the application. Then we simply call the static method Launcher.openFile(file) which does the rest. This has been verified to work on windows, apple, and linux platforms reliably.
The Launcher utility in addition provides the following useful two methods:
  1. public static void openInBrowser(String url)
  2. public static void openInEmailApp(EmailMessage msg)
    public static void openInEmailApp(String mailtoURL)

12.6  Summary

In this chapter we've seen the ease with which we can integrate wizards (assistants) into an already powerful user interface.
This wizard feature has already been used in other contexts to produce complex wizards, that break down complex new patient forms at medical clinics into a set of smaller steps. Most of us are too familiar with the amounts of information a patient or guarantor must enter when first visiting a medical institution (in the United States, at least).
The JMatter framework attempts to be as open as possible. Wizards are not the only way to inject custom user interface features into JMatter applications. JMatter was designed to allow you to write your own custom views for objects and plug them into your existing JMatter applications, which is the topic of our next chapter.
We've also seen the CSV Export feature, a nice though simple mechanism for exporting data out of your application. Of course, there already exist many tools to export and process your application's data. It's already easily accessible in that all the information resides in an open database system.
Finally, we also see that JMatter provides an avenue for producing PDFs by integrating and leveraging the open source JFreeReport API. I've personally also written directly against the underlying iText PDF library to produce and display PDFs directly as a consequence of a command invocation.

HEAD NEXT