In the previous sections some of the key concepts of the JGUIraffe library have been described. Allthough there have been some code examples, there has not yet been a complete example that constructs a full user interface and combines the elements discussed so far. The reason for this is that the single elements of a JGUIraffe application are typically not created by hand. Rather, this is the task of so-called builders.
The JGUIraffe library uses the term builder for a component that is capable of interpreting a builder script and producing objects from it. The results of such a builder operation can be graphical components which are part of the user interface. They can also be plain old Java objects (POJOs) implementing business logic.
For instance, if an application wants to display a dialog box, there is no
code that creates the dialog window, initializes the graphical components,
associates them with a Form
object, and so on. Instead the
application has a builder script that contains all relevant definitions.
This script is passed to the builder. When the builder completes the fully
initialized dialog window with all its helper objects is available and can
be displayed.
In this section we give a high-level overview over builders and how they are used to bring the concepts discussed so far together to form a fully functional application. Subsequent sections will then deal with specific builder types and provide detailed information about constructing different kinds of elements.
If you have ever implemented a user interface - e.g. a Swing UI - directly in Java, you will certainly agree that the resulting Java code can be, well, problematic. It is hard to read as it is not obvious which parts of the code are responsible for which parts of the UI. And it is even harder to maintain or to incorporate changes of the user interface. Therefore the authors of this library belief that there should be a separation between Java code and the definition of the user interface. This can lead to the following advantages:
In the first line builders make the construction of a user interface element
like a dialog box very convenient and reduce the amount of Java code
required for this purpose significantly. They also ensure that all the
helper components provided by the JGUIraffe library to simplify UI
programming are correctly created, initialized, and wired together. For
instance, when a window for a dialog box is constructed, the builder
automatically creates a corresponding
Form
object which can be used to read or write the contents of the graphical input
components. A controller object for the dialog box can be created, too.
So builders link the different parts of a JGUIraffe application together. The result of a builder operation can be used directly by an application without additional initialization or configuration.
A builder script contains definitions for the objects (graphical components or POJOs) to create. The default builder implementation in JGUIraffe is based on the Apache Commons Jelly project. This means that builder scripts are actual Jelly scripts, i.e. XML documents.
Jelly provides a powerful and flexible way of processing XML documents. The basic idea is that XML elements are mapped to Java classes - so-called tag handler classes. The Jelly engine processes the XML document, inspects the single XML elements, creates the corresponding tag handler objects and invokes them. This basically means that the XML document is executed as if it was a program written in a specialized XML-based programming language.
Jelly ships with many standard tag handler classes organized in different tag libraries. A tag library contains several related tag handler classes and associates them with an XML namespace. (The XML elements in a Jelly script are all prefixed with a namespace indicating the tag library they belong to. This makes it possible to have tags with identical names in different tag libraries.) There are tag libraries defining core programming constructs (like conditional execution, loops, or including other scripts) and other, very specialized tag libraries as well. The latter include tag libraries for XML processing, thread handling, accessing HTML pages, sending e-mails, and many more. All these standard tag libraries can be used in builder scripts.
Writing a custom Jelly tag handler class is not complicated. The class can
extend the base class org.apache.commons.jelly.TagSupport
. Then
it has to define the doTag()
method. Here arbitrary logic of
the tag can be implemented.
The JGUIraffe library contains some custom tag libraries that deal with the creation of several objects. The following table lists these specific tag libraries and provides a short description for each. They are discussed in detail in later sections.
Name space | Library name | Description |
---|---|---|
di | Dependency injection | The tags in this library allow the creation of arbitrary Java objects. Java beans can be declared, and their properties can be set. References to other beans can be defined which are automatically resolved by the framework. Thus complex networks of objects can be defined and accessed in a convenient way. |
f | Form components | This tag library contains tags for creating typical UI controls to be used in forms, e.g. labels, text fields, lists, panels, check boxes, etc. It is the most comprehensive tag library provided by JGUIraffe. This is due to the amount of available UI controls. |
a | Actions | The Action tag library supports the definition of actions, i.e. elements the user can interact with to trigger a functionality of the application. The declaration of menus and tool bars is also covered. |
w | Windows | Here tags for creating several kinds of windows - e.g. frames or dialog boxes - can be found. This library also supports the creation of controller objects - Java classes that are associated with windows and control their life-cycle. |
The following listing shows a simple (incomplete) builder script. This is just to give the reader an impression how such a script looks like. Details will be discussed in later chapters.
<?xml version="1.0" encoding="ISO-8859-1"?> <j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder" xmlns:f="formBuilder" xmlns:a="actionBuilder" xmlns:w="windowBuilder"> <!-- The dialog window --> <w:dialog titleres="newfile_title" center="true"> <f:borderlayout/> <!-- The main panel --> <f:panel> <f:borderconstr name="CENTER"/> <f:percentlayout columns="4dlu start/preferred 3dlu full/preferred(6cm)/100 4dlu" rows="4dlu preferred 3dlu preferred full/preferred(5cm)/100 4dlu"/> <f:label textres="newfile_lab_name"> <f:percentconstr col="1" row="1"/> </f:label> <f:textfield name="fileName" displayNameres="newfile_disp_name" maxlength="200" tooltipres="newfile_txt_name_tip"> <f:percentconstr col="3" row="1"/> <f:validators phase="syntax"> <f:validator class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/> </f:validators> </f:textfield> <f:label textres="newfile_lab_content"> <f:percentconstr col="1" row="3"/> </f:label> <f:textarea name="fileContent" displayNameres="newfile_disp_content"> <f:percentconstr col="1" row="4" spanx="3" targetCol="3"/> </f:textarea> </f:panel> <!-- The button bar --> <f:panel> <f:borderconstr name="SOUTH"/> <f:buttonlayout/> <f:button name="btnOk" textres="newfile_btn_create" mnemonicres="newfile_btn_create_mnemo" default="true"/> <f:button name="btnCancel" textres="newfile_btn_cancel" mnemonicres="newfile_btn_cancel_mnemo" cancel="true"/> </f:panel> </w:dialog> </j:jelly>
This script defines a simple dialog window with a text field for entering a file name and a text area for the file content. The PercentLayout manager is used for laying out the components. The dialog also has two buttons for creating the file or for canceling the operation.
Real-world builder scripts tend to be a bit larger because typically some helper objects (e.g. controllers, actions, validators, etc.) are defined, too. This is actually one disadvantage of this approach: builder scripts can become pretty verbose. Jelly provides some means for addressing this problem. For instance, it is possible to devide the functionality in multiple sub scripts which are then included in the main script.
Access to a builder is possible through the
Builder
interface. This interface defines the following methods for
creating objects:
build()
is a generic builder method. It processes a builder
script that may define arbitrary objects. Access to the objects created by
the builder is possible through the data objects passed to the method (see
below).buildContainer()
can be used for constructing a UI if the
root container (e.g. a panel) is already available. The root container is
passed to the builder. Graphical components defined in the builder script
are then added to this container.buildWindow()
is a convenience method for creating a
complete window. The method expects that the builder script to be processed
defines a kind of window. This window is returned.buildContainer()
and buildWindow()
are merely
convenience methods which can be implemented in terms of the generic
build()
method. In addition to these methods there is a
release()
method which can be used to free all resources
obtained by objects created by the script. Usually the developer does not
have to call this method explicitly because this is done by the framework.
A builder needs a lot of parameters to fulfill its task. When invoking the
builder these parameters have to be passed in form of a
BuilderData
object. The BuilderData
object is pretty
complex. It does not only define input parameters for the builder but also
allows access to the builder results. Fortunately the major part of the
input parameters can be inferred from global application data. Therefore it
is not necessary to create and populate a BuilderData
object by
hand. Rather, there is a convenience method in the
ApplicationContext
class that returns an initialized object. We
will show this later in an example.
Another important information required by the builder is of course the
script to be processed. Typically these scripts are located in the file
system or in the class path of the application. JGUIraffe
provides an abstraction for the concrete location of a builder script in
form of the
Locator
interface. There are multiple concrete
Locator
implementations, including a locator for files, for
URLs, or for scripts in the application's class path. Another interesting
Locator
implementation is
ByteArrayLocator
. This class allows passing a script to a builder
that was created in memory, e.g. as a string.
These are the most important interfaces related to builders. There are a couple of other interfaces involved. These are discussed in the details section.
To invoke a builder in order to execute a builder script the following steps have to be performed:
Builder
object has to be obtained.
BuilderData
object required by the builder has to be created and
initialized..
Locator
object pointing to the builder script has to be created.build()
method of the builder has to be
called.
All these steps can be done manually. For instance, the various parameters
managed by a BuilderData
object could be set by hand.
Fortunately, this is not necessary because
ApplicationContext
provides methods that do the major part of the
work already. So just a few lines of code are needed as shown in the
following code fragment:
Builder builder = application.getApplicationContext().newBuilder(); ApplicationBuilderData builderData = application .getApplicationContext().initBuilderData(); Locator locator = ClassPathLocator.getInstance("mywindow.jelly"); Window window = builder.buildWindow(locator, builderData);
In this example application
refers to the global
Application
object. First a new Builder
object is
obtained using the newBuilder()
method of the application
context. Then the initBuilderData()
method of
ApplicationContext
returns an initialized object of the
ApplicationBuilderData
class which is a default implementation of
the BuilderData
interface. The object returned by
initBuilderData()
contains already all information required by
the builder. So it can be used directly. Alternatively it is possible to set
further properties or change some of the default settings before the builder
is actually invoked.
Next a Locator
object is created pointing to the builder script.
In this case a script named mywindow.jelly is to be executed which
is located in the application's class path. If the application is packaged
in a jar archive, the script is probably also part of this jar. The
ClassPathLocator
class searches the whole class path for a
resource with the specified name.
Now all information required for executing the builder script is available.
In the last line the builder is called. Here the buildWindow()
method is called which returns the main window defined in the builder
script. (We assume that the script named mywindow.jelly actually
defines a window.) The window returned by the builder can now be used. If
something goes wrong - for instance, if the script cannot be found or
contains invalid instructions -, a
BuilderException
is thrown.
For creating and opening a window - e.g. a dialog box - there is an even
more convenient way. Because this is a use case needed frequently by a
typical application the JGUIraffe library provides a convenience
class which does exactly the steps outlined above:
OpenWindowCommand
.
As the name implies, OpenWindowCommand
is a
command object. Therefore it can be executed in
a background thread. (Executing a builder script should normally be very
fast. However, it does not hurt to do this in a background thread.) When an
instance is created the Locator
object defining the builder
script must be provided. Then the command can be executed, and the newly
created window is displayed automatically.