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:
- Enter person's name
- Enter person's physical address
- Enter remaining contact information (phone numbers, email address)
- 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:
- create each step
- add the steps in the proper order
- 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:
- we create a new PersonContact instance
- we set the name, contact, and address values
- 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.
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.
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.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.
JFreeReport provides two ways in which data can be passed in to its
xml report specification:
- the implementation of a TableModel interface, which provides
the majority of the tabular data to be included in a report, and
- 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.
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:
- public static void openInBrowser(String url)
- public static void openInEmailApp(EmailMessage msg)
public static void openInEmailApp(String mailtoURL)
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.