Section III: In Depth

Styles

Most applications try to use consistent styling and Aria provides support for consistent coloring and typography through its style manager. Aria's style management allows all the information and details of colors and fonts to be maintained separately from the rest of the UI description. Not only does this make it easy to reference particular style elements but it also makes it easy to switch styles say, for example, when rebranding an application for a different sets of end-users.

Using styles

In the introduction section we saw how styles could be applied when adding components either via XML or within Java code. Aria provides interactive visual tools to help design and use styles. Within Aria a style can be applied by selecting a component or several components and clicking on the chosen style in the style palette. The style name is then linked to the component so that any subsequent change to the style will change the look of linked components. If a component has an associated style then the styles palette's selection will be updated to show the selected style. The style palette presents a preview of the style and if you move the mouse over an individual style you should see a tooltip presented using the appropriate style, including the style's font.

Changing a style

A style can be modified by selecting the style in the style palette and then right clicking to popup the style's context menu. For most styles the simplest thing to do is to choose the Edit... option and pick colors and fonts that suit the particular use. The style edit dialog allows you to choose the font face and style plus the foreground and background colors. The styles are then saved to the styles file whenever the project is saved.

Color schemes

Aria extends the notion of styles by including a color scheme chooser to provide coherent sets of styles.

You can edit a set of styles as a color scheme by choosing the Color Scheme... option from the Style Palette's popup menu. The Color Chooser provides a convenient way of specifying sympathetic systems of colors and styles. The chooser consists of a number of areas for choosing the colors and then applying these colors to a set of styles. The color chooser has two parts, the first allows the choice of a set of sympathetic colors and the second allows selection of these colors for various usages. On the left hand side is a color wheel where you can choose a base color. This base color can be manipulated using two sliders for brightness and saturation. The hue can also be modified by clicking at some point within the inner circle. The Hue, Saturation and Brightness values can also be entered directly as numeric values. Clicking on the selected color preview (just below the color wheel) pops up a list of system colors (see the image on the right). The system colors can also be selected whenever the color wheel is available. Once the color has been chosen the colors and the variants are shown in a preview area. The number and type of variants displayed depend on the color scheme chosen. By default the scheme is monochromatic but other schemes can be chosen from the drop down list at the top of the chooser. Below the preview area is a table of colors and you can click on any on these to use it as the base color. On the second page of the chooser is an area where you can configure the styles. Simply click on the various sample texts to modify the foreground color, the background color or the font. A set of radio buttons allow you choose from these options. Once the set of styles has been selected click OK to save the styles under the specified style name. If a new style is being created the styles will be added to the style palette. Alternatively if the style name is already in use then the styles will be updated once the chooser is closed.

Loading styles

The style details are stored in an XML file pointed to by the startup parameter ' StyleFile '. In the absence of an entry or a file name the files ' styles.xml ' is used. A typical style file includes color and font information as below: A sample style file

  1. <styles>
  2. <style name="banner">
  3. <color_back value="ffffff"/>
  4. <color_fore value="0000ff"/>
  5. <font_face value="arial"/>
  6. <font_size value="10"/>
  7. <font_weight value="0"/>
  8. <font_italic value="0"/>
  9. </style>
  10. <style name="base">
  11. <color_back value="ffffff"/>
  12. <color_fore value="0000ff"/>
  13. <font_face value="arial"/>
  14. <font_size value="10"/>
  15. <font_weight value="0"/>
  16. <font_italic value="0"/>
  17. </style>
  18. <style name="Caption">
  19. <color_back value="ffffff"/>
  20. <color_fore value="008800"/>
  21. <font_face value="arial"/>
  22. <font_size value="11"/>
  23. <font_weight value="0"/>
  24. <font_italic value="0"/>
  25. <style name="CaptionSmall">
  26. <font_size value="8"/>
  27. <font_italic value="1"/>
  28. </style>
  29. </style>
  30. <style name="footer">
  31. <color_back value="ffffff"/>
  32. ...
  33. </styles>

Styles are determined from this hierarchy so that in the above example the style ' CaptionSmall ' is of the same font and color except with a smaller point size and italicized. (as of v2.0.6) Within the style file XML a color can also be specified as a decimal RGB value, an alpha channel value can also be specified as an option decimal value or as a hexval: Using decimal RGBA values

  1. <styles>
  2. <style name="banner">
  3. <color_back value="255,255,255"/>
  4. <color_fore value="0,0,255"/>
  5. <font_face value="arial"/>
  6. <font_size value="10"/>
  7. <font_weight value="0"/>
  8. <font_italic value="0"/>
  9. </style>
  10. <style name="base">
  11. <color_back value="ffffff80"/>
  12. <color_fore value="0,0,255,192"/>
  13. ...
  14. </styles>

Using System colors

System colors can also be used by naming the system color in place of an RGB value, thus Using SystemColors

  1. <styles>
  2. <style name="banner">
  3. <color_back value="activeCaption"/>
  4. <color_fore value="activeCaptionText"/>
  5. <font_face value="arial"/>
  6. <font_size value="10"/>
  7. <font_weight value="0"/>
  8. <font_italic value="0"/>
  9. </style>

The system colors are dependant on the user preferences and may vary from user to user and machine to machine. The system color properties are:

System Colors

Name

Usage

desktop

The color rendered for the background of the desktop.

activeCaption

The color rendered for the window-title background of the currently active window.

activeCaptionText

he color rendered for the window-title text of the currently active window.

activeCaptionBorder

The color rendered for the border around the currently active window.

inactiveCaption

The color rendered for the window-title background of inactive windows.

inactiveCaptionText

The color rendered for the window-title text of inactive windows.

inactiveCaptionBorder

The color rendered for the border around inactive windows.

window

The color rendered for the background of interior regions inside windows.

windowBorder

The color rendered for the border around interior regions inside windows.

windowText

The color rendered for text of interior regions inside windows.

menu

The color rendered for the background of menus.

menuText

The color rendered for the text of menus.

text

The color rendered for the background of text control objects, such as textfields and comboboxes.

textText

The color rendered for the text of text control objects, such as textfields and comboboxes

textHighlight

The color rendered for the background of selected items, such as in menus, comboboxes, and text.

textHighlightText

The color rendered for the text of selected items, such as in menus, comboboxes, and text.

textInactiveText

The color rendered for the text of inactive items, such as in menus.

control

The color rendered for the background of control panels and control objects, such as pushbuttons.

controlText

The color rendered for the text of control panels and control objects, such as pushbuttons.

controlHighlight

The color rendered for light areas of 3D control objects, such as pushbuttons. This color is typically derived from the control background color to provide a 3D effect.

controlLtHighlight

The color rendered for highlight areas of 3D control objects, such as pushbuttons. This color is typically derived from the control background color to provide a 3D effect.

controlShadow

The color rendered for shadow areas of 3D control objects, such as pushbuttons. This color is typically derived from the control background color to provide a 3D effect.

controlDkShadow

The color rendered for dark shadow areas on 3D control objects, such as pushbuttons. This color is typically derived from the control background color to provide a 3D effect.

scrollbar

The color rendered for the background of scrollbars.

info

The color rendered for the background of tooltips or spot help.

infoText

The color rendered for the text of tooltips or spot help.

Extended Styles

Styles can be extended to add attributes other than the basic color and font attributes. However the use of these extra attributes is dependant upon the context such as the component or class using the style. The extra attributes are automatically added for the style when the style is loaded. Once the stle has been loaded it is marked as closed and any attempt to access an unrecognised style name will cause an error (this can be changed with the XStyleEx.setClosed(...) method). An example usage of the extended styles is the validation style, which uses three colors to specify the feedback colors for the validation states. The validations styles could thus be specified as: Specifying extended styles

  1. <style name="banner" extended="true">
  2. <colorNormal value="ffffff"/>
  3. <colorWarn value="ffffd5"/>
  4. <colorFail value="ffe6d5"/>
  5. </style>

Note the extended attribute for the style. By adding extra style attributes you may be able to provide additional consistency within the application as attributes for various classes can be specified on an application wide basis instead of via a class by class or component by component basis. In many cases you can specify the attribute values for components, for example alignments and borders. In applying a style the framework will attempt to set component attributes using the style name and value via either the XAttributedComponent interface or via Reflection. The basic types such as String, int, double, float and boolean values can be accomodated. The built-in styles are handled directly as described above and set the component's colors and font. Setting right aligned label styles and properties

  1. <styles></P>
  2. <style name="labelBold">
  3. <color_back value="255,255,255"/>
  4. <color_fore value="0,0,0"/>
  5. <font_face value="arial"/>
  6. <font_size value="10"/>
  7. <font_weight value="1"/>
  8. <font_italic value="0"/>
  9. <alignment value="Right"/>
  10. </style></P>
  11. ...

Applying styles

To apply a style to a component you just need to reference the style in the page's XML declaration. Thus applying style to an address form becomes: Style usage

  1. <Page>
  2. <Components>
  3. <Label x="42" y="61" w="103" h="20" content="First Name:" alignment="Right" style="Caption"/>
  4. <Label x="42" y="85" w="102" h="20" content="Last Name:" alignment="Right" style="Caption"/>
  5. <Label x="42" y="116" w="102" h="20" content="Telephone:" alignment="Right" style="Caption"/>
  6. <Label x="42" y="137" w="102" h="20" content="Fax:" alignment="Right" style="Caption"/>
  7. <Label x="42" y="161" w="102" h="20" content="Mobile:" alignment="Right" style="Caption"/>
  8. <Label x="42" y="196" w="102" h="20" content="Address:" alignment="Right" style="Caption"/>
  9. <Edit name="firstNameEdit" x="154" y="62" w="100" h="20"/>
  10. <Edit name="lastNameEdit" x="154" y="85" w="100" h="20"/>
  11. <Edit name="phoneEdit" x="155" y="115" w="100" h="20"/>
  12. <Edit name="faxEdit" x="155" y="137" w="100" h="20" />
  13. <Edit name="mobileEdit" x="154" y="160" w="100" h="20" />
  14. <Edit name="address1Edit" x="155" y="198" w="248" h="20" />
  15. <Edit name="address2Edit" x="155" y="222" w="248" h="20" />
  16. <Edit name="address3Edit" x="155" y="246" w="249" h="20" />
  17. </Components>
  18. </Page>

Related issues

Colors and fonts are not the only style related things that should be consistent within an application. Layouts, naming, data representations, translations and so on should also be consistent. Consistency will make it easier for a user to grasp the concepts behind the application and it will make it easier to understand the mechanism used within the application for various tasks. Logically grouping elements can also assist readability. So, for example placing related components together within a framed panel gives a visual clue as to the association of those components. Subtle shading of panels can also assist in giving the necessary visual clues about the role of an individual panel particularly if the coloring is reinforced by the behavior you code into the applications.

Layout

In terms or readability it is often said that the average person can optimally perceive about seven pieces of information. In an application it is often difficult to limit the number of items or components displayed at once. Therefore style can play an important role in reducing apparent clutter and confusion. The style manager can go along way to helping produce a harmonious color scheme and if judiciously employed such a scheme may allow you make better use of the on-screen space. To maximize such benefit it is important that your layout is consistent. Careful consideration should be given to alignments, spacing and grouping of components. All of these features can greatly assist in the smooth flow of action within an application. A later chapter (See Layout.) details how layouts can be controlled within an application. With dynamic content it is always important to consider how the content will appear. Aria makes it easy to dynamically modify the content of a component but this generally doesn't mean that the layout of that component will be modified. Particularly for elements that display text it is important to note the interaction of fonts and layouts. Some fonts are narrower than others or have different distributions of character widths and therefore it is important to test your application with a representative set of data.

Flow

Just as layout is important, the flow of text and components can have a major impact on the user experience. Generally it is best to keep it simple: left, right, top to bottom. Keep it simple, reinforce the visual signals, update consistently.

Naming

Styles should be named consistently for two reasons. Firstly from a programming point of view a consistent naming convention will make reading of code easier and hence development and maintenance will be easier. We recommend that you use a convention similar to the naming convention you would use in programming Java. Secondly a consistent style naming convention is important where styles are being modified and particularly where the Color Chooser is being used. The chooser can modify existing styles depending on the style name and therefore a consistent naming convention will help avoid confusion when styles are modified in this way.

Data/formatting

Some values such as numeric, date or monetary values may be formatted specially and styles can be used to reinforce or highlight important values such as negative monetary values.

Setting the system look and feel

The system look and feel can be set up using a look and feel (LAF) installer. The LAF installer is controlled by a startup parameter " LAF ". The value of this parameter refers to a helper class that installs the LAF and takes care of the initialization. This setting is only appropriate for Swing applications as changing the LAF is not supported in the AWT.

Setting the Look and Feel

In Swing you can alter the look and feel of an application by setting the 'Look and Feel' manager for the application. The look and feel (LAF) affects the visual appearance of components and some of the soft behavioral attributes, like highlighting, rounding of button edges and so on. The LAF may also have a broader impact by changing indentations, component sizes and layout. Setting the LAF is fairly straightforward within Aria. The project settings pages include the option to choose the LAF. (it is on the files page as somewhat tenuously you need to choose a configuration file for the synth LAF) Adding a new LAF is also possible and Aria makes this even easier than normal by allowing you to simply plug-in the look and feel via a startup parameter. Aria comes with support for a few popular LAFs, but it is also easy to add support for your favorite LAF. The component class for setting the LAF is part of the Open Source Aria and hence you can review the source code for the LAF installer to see how to build an installer for some other LAF. The LAF is set with the following startup parameter

Setting the look and feel

LAF=org.formaria.laf.SynthInstaller

This setting refers to a helper class that installs the LAF and takes care of any initialization that is necessary. Aria lists a few popular LAFs, including the default LAF, JGoodies Looks LAF, Windows and the Synth LAF. Of these, the Synth LAF is probably the most interesting as it is configured via XML.

Configuring Synth

The Synth LAF requires a number of extra parameters to be configured to operate correctly.

Synth parameters in the start-up file

SynthConfigFile=demo.xml SynthResourceLoader=mypackage.MyClass

These parameters may vary significantly depending on the content of the Synth configuration file. The SynthResourceLoader is the name of a class that will be used to load the resources used by the Synth LAF. The class does not need to do anything special as it merely provides a (classloader) route to the resources. When the Synth LAF is chosen a number of new styles are added to the project if they do not already exist. These styles are used by the LAF and you can customize them to alter the look. Note that the Synth LAF provides rendering for many aspects of the components and the LAF may therefore override the styles you have chosen for individual components. Some of the built-in components have an option to suppress the LAF and use the styles you explicitly apply to the component.

SkinBuilder

A sample project ` SkinBuilder ' is included in Aria 3.0, this application is another demo of Aria, but it can be used to configure the Synth Look and Feel, including choosing artwork, setting properties and colorizing the LAF . See the accompanying documentation for more details.

Using styles with Synth

While the Synth look and feel can be used out of the box it is often desirable to connect the look and feel to the application's styles for added consistency. The Synth look and feel is configured via an XML file and this configuration file can itself be preprocessed to include colors and fonts from the applications look and feel. Some special expressions can be embedded in the XML and when processed these are converted into the color and font declarations expected by Synth. Scalable Vector Graphics ( SVG) images can also be used with Synth by way of the preprocessor. Aria can convert a referenced SVG into a PNG raster graphic for use in the final application. Using SVG in this way allows you to create sophisticated icons and buttons for your application and have these images scale to the required size.

Synth substitution expression

Expression

Example Result

Usage

${getFontFace(styleName)}

Arial

Gets the font face name from the style

${getFontSize(styleName)}

10

Get the font point size from the style

${getColorBackground(styleName)}

F0C2E9

Get the foreground color from the style

${getColorForeground(styleName)}

F00000

Get the background color from the style

${getFontWeight(styleName)}

BOLD

Get the font weight from the style

${getFontItalic(styleName)}

ITALIC

Get the font italic flag from the style

${svgToPng(styleName)}

images/button.png

Convert an SVG image to a PNG. Optional width and height attributes can also be specified after the source image name. If they are omitted a 50x50 image is created. A PNG image sometimes includes an optional preview of the image.

${svgToTiff(styleName)}

images/button.tif

Convert an SVG image to a TIFF. Optional width and height attributes can also be specified after the source image name. If they are omitted a 50x50 image is created.

Using SVG and Synth

Just as the Synth file can be processed so to can the preprocessed SVG files. SVG uses a slightly different definition of colors so different expressions are required but the concepts are the same. The substitution expressions also come in a number of variations to help support generation of gradients from a single style.

SVG substitution expression

Expression

Example Result

Usage

${getColorForegroundRgb(styleName)}

rgb(108,128,255)

Gets the RGB foreground color from the style

${getColorForegroundHex(styleName)}

F0C2E9

Gets the HEX foreground color from the style

${getColorBackgroundRgb(styleName)}

rgb(108,128,255)

Gets the RGB background color from the style

${getColorBackgroundHex(styleName)}

F00000

Gets the HEX backgroundcolor from the style

${getHsbColorForegroundRgb(styleName)}

rgb(108,128,255)

Gets the RGB foreground color from the style

${getHsbColorForegroundHex(styleName)}

F0C2E9

Gets the HEX foreground color from the style

${getHsbColorBackgroundRgb(styleName)}

rgb(108,128,255)

Gets the RGB background color from the style

${getHsbColorBackgroundHex(styleName)}

F0C2E9

Gets the HEX background color from the style

Each expression in the table above can have just a style name as an argument or it can have the style name plus percentage values for each color channel. Thus an expression such as getColorForegroundRgb(HeadlineStyle,50,60,80) obtains the color from the style, splits out the RGB color channels and returns a color that scales the channel values by the percentages given. So if the red value was 240 the example expression would return a color with a red value of 50% or 120. Similarly if the percentage given is between 100% and 200% the color value is scaled up to 255. In this way a red value of 120 and a percentage of 150 would result in a color with a red value of 187 (i.e. 120 + (255-120)*50/100). The expression set also includes a variation where instead of scaling the RGB values the HSB values are scaled. In some cases it may be easier to specify the gradient color stops as variations in saturation or brightness values. The scaling of the HSB values works in the same way as the RGB values. Aria automatically adds a set of styles for Synth when you select the Synth LAF. These styles are the styles that are used in the preconfigured Synth LAF that is part of Aria. However you can further customize the look and feel to use whatever graphics files and styles that you like, the configuration file is just another XML file that you can edit. To add another style to the Synth LAF just add the matching style within Aria or within the style file. Then embed the relevant expression from the list above in your Synth configuration file. The Synth LAF requires the Batik SVG engine to process SVG images. The libraries for Batik must be part of your project for this process to work correctly. More information on Batik can be found on the Batik website.

Panel backgrounds

Synth can be used to paint many components including the panel backgrounds but in some cases a different look or style is desirable for painting panels to help distinguish part of an application. A custom painter class can be used for such painting, the code for configuring such a background painter is shown below:

Setting a background painter

The example below shows a number of panels using this custom painter to display a gradient background with a watermark logo.

Painters

Aria 2.0 introduced the notion of painters much like the painters used within the Synth look and feel, and since then the use of painters has become more widespread. Aria 3.0 adopted the SwingLabs painter API in an effort to conform to standards and to make a wider range of painters available. The painters API is straightforward.

The painters API

public void paint( Graphics2D g2d, JComponent comp, int w, int h );

and can be used in a varient of Aria and Aria components. The painter is normally responsible for painting the entire client area of the component, but of course it is open to configuration. The painter class need not extend a component and therefore it acts purely as helper class. Some painters included in Aria are:

 

Painter

Appearance/Role

BandPainter

Fills the client area with a number of coloured bands, graduating from the foreground colour to the background colour.

FlarePainter

Fills the client area with a radial gradient using the foreground and background colours

TitlePainter

Extends the GradientBackground by adding a caption

GradientBackground

Fills the background with a gradient. The gradient uses the background and foreground colours. The painter can be configured to `nest' its gradient into the parent coordinate space so that multiple panels appear to share the same gradient.

SvgPainter

A painter based on an SVG images. See the section SVG Support for more details. The painter can interact with the mouse for various effects.

ImagePainter

Paints an image in the client area.

Style guidelines

A full discussion of styles is beyond the scope of this guide, however a number of style guidelines have been published and depending on your target platform and personal tastes it may be worth consulting these guidelines for clues as to best practice.

Look and feel resources

Java Look and Feel Design Guidelines: http://java.sun.com/products/jlf/

Introduction to the Apple Human Interface Guidelines: http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/

Official Guidelines for User Interface Developers and Designers: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/welcome.asp

Palm OS User Interface Guidelines: www.palmos.com/dev/support/docs/uiguidelines.pdf

SwingLabs Painter http://javadesktop.org/swinglabs/build/weekly/latest/swingx-HEAD/javadoc/org/jdesktop/swingx/painter/package-summary.html

 

Layout Managers

Layout

By default Aria uses absolute positioning of components, with each component having explicitly specified size and location. Frequently it is more convenient to use some of the additional layout facilities found in Aria and in Java to both improve the overall appearance and simplify development.

Aria even provides its own layout manager to support interactive visual layout above and beyond what is possible with explicit layout while providing the runtime resize and position behavior that are frequently hard to achieve with other layout managers.

Default layout

As mentioned in the introduction Aria assumes that each component's coordinates are specified explicitly. Simply speaking this means that each component must have a specified X, Y location and a width and size dimension. The dimensions are all in pixels and do not vary with the size of the application window or the page container.

Limitations of explicit layout

Perhaps the greatest limitation of explicit layout is that the pages do not resize with changes to the application window and therefore if the application is made full screen it does not make good use of the available screen space.

Another significant limitation of the explicit layout is that it is not always possible to know the size needed for a component or a component's content in advance. Dynamic sizing of components can alleviate such problems.

Later in this chapter we will see how Java's Layout Manager facility has been adapted to help address some of the issues.

Aria provides a number of tools to support interactive positioning of controls. The tools include grids and guides. These tools can be used with several of the supported layout managers but work best with Aria's own layout managers; the GuideLayout and the ScaleLayout.

Alignment tools

Aria provides several tools for aligning components within a container and even spanning containers. These tools are located on the toolbar within the page designer and perform the following functions:

Alignment and Justification Tools

Title

Usage

 

Align Bottom - aligns the bottom edges of all the selected components. The components are moved and retain their current height.

 

Align Centre - aligns the centres of all the selected components. The components are moved and retain their current width.

 

Align Left - aligns the left edges of all the selected components. The components are moved and retain their current width.

 

Align Right - aligns the right edges of all the selected components. The components are moved and retain their current width.

 

Align Top - aligns the top edges of all the selected components. The components are moved and retain their current height.

 

Justify Horizontal - aligns the left and right edges of all the selected components. The components sized according to the minimum left and maximum right hand coordinates.

 

Justify Vertical - aligns the top and bottom edges of all the selected components. The components sized according to the minimum top and maximum bottom coordinates

 

Space Vertical - Adjusts the space between the selected components so that the space is equal distributed along the vertical axis

 

Space Horizontal - Adjusts the space between the selected components so that the space is equal distributed alongthe horizontal axis

The alignment and justification tools are primarily designed to work with explicit layouts and will adjust the coordinates of the selected components. Use of the alignment tools with pages controlled by layout managers can lead to results that vary according to the specific layout manager and its current settings.

Grids and Guides

In the page designer the page being edited can be shown within a grid and with layout guides. The designer shows rulers to the top and left of the page, set out in pixel units.

The designer allows components to optionally snap to the grid. The selection of this option and the granularity of the grid can be configured within the page designer. In the top left hand corner or the designer is a button, that when pressed toggles the display mode. When in the configuration mode the rulers are shown with a red tint.

Once in the configuration mode you may right click anywhere on the page to get a context menu giving some options to control the layout. Once configuration has been completed just click the button in the top left to return to the page editing mode.

The options that can be configured in this way include:

 

Option

Role

Show Guides

Shows the alignment guides

Show Grid

Show the grid (with a minimum display spacing of 10 pixels).

Snap to grid

Turns on or off the snap to grid facility where by components that are moved align themselves to a grid point.

Snap to guides

Turns on or off the facility whereby components snap to the nearest guide.

Set Grid Spacing

The separation of grid points in pixels.

Guide sensitivity

Specifies the distance in pixels within which a component must be for it to be affected by a guide line.

Setup Columns

Displays a dialog that allows the addition or guides to support rows and columns for layout purposes

Guides

Drawing on the techniques used in desktop publishing and document layout, Aria includes a facility to use guides. Guides are vertical or horizontal lines on the page to which components snap or gravitate while being positioned during page layout.

Guides are configured on a page by page basis rather than at the level of individual panels. Guides therefore support layout across panels so it is easy to align components on a page wide basis rather than solely within an individual container as is the case with most layout managers. Guides are also visually intuitive so little effort is required to learn how guides function and indeed there should be few surprises when using guide layouts unlike layouts such as the GridBagLayout which is notoriously difficult to use.

Adding guides

Guides can be configured on a page by page basis or on an application wide basis (as master guides) to help enforce layout rules across pages.

Within the page designer adding guides is carried out by

  1. Clicking the button in the top left intersection of the rulers.
  2. Click and drag from either of the rulers onto the page
  3. Release the mouse at the location for the new guide
  4. Drag an existing guide to adjust its position
  5. Click the button in the top left to exit the guide editing mode.

To remove a guide simply enter the guide editing mode and drag a guide back to the ruler.

Individual guides can then be configured by right clicking on them and choosing the Guide Options... menu option. This option allows control over how the guide is positioned at runtime.

Guides can be positioned relative to one another or relative to the size at which the page is displayed so as to give a controlled final layout. The coordinates system used for the guide can also be expressed in relative terms of the page size instead of the absolute pixel size used at design time. The guide ID shown in the dialog is used to reference the guide by the constraint specified for each component on the panel controlled by the guide layout manager.

Guide options

Parameter

Usage

ID

An ID used by the component constraint to identify the guide. This option is read-only and is automatically configured by the editor. Guides are renumbered as they are repositions so that the leftmost or topmost guide is always zero.

Orientation

A read-only property identifying the guide as vertical or horizontal

Position

The guide position. Absolute positioning means the guide is position relative to the top left of the page. Relative positioning means the guide is position relative to the previous guide (to the left or above).

Coordinate

The coordinates express the position of the guide either in absolute terms of pixels or in terms of the page size (scaled). For a scaled coordinate system the location is expressed as a percentage of the page size.

Location

The guide location is its position. For a relative position the location value is the offset from the previous guide, whereas for an absolute location the value is the distance from the top left of the page.

Minimum

Not yet implemented

Maximum

Not yet implemented

Several more options are available for configuration of the guides:

The area of guide sensitivity, that is the area within which a component will snap to the guide when moved can be highlighted (as illustrated above) by choosing the Show snap zone context menu option. The context menu is displayed by right clicking on a guide in edit mode. To enter the guide's edit mode, toggle the button to the top left of the page at the vertex of the two rulers. In this mode the rulers are shown with a red background. Some of the guide options are context sensitive and depend on the guide on which the mouse is clicked. The options are described below:

Guide configuration options

Menu option

Function

Snap to guides

Toggles snapping of components to the guides

Snap to grid

Toggles snapping of components to the grid.

Snap to hints

Snap the component to the hints. Matches one component to the edges of another.

Show guides

Toggle display of the guides

Show snap zone

Toggle display of the zone within which components snap to the guides.

Show grid

Toggles display of the grid. The grid is shown as a set of dotted lines. Note that at lower grid spacings not every grid line is shown as doing so would obscure the display.

Set grid spacing

Set the grid spacing in pixels

Setup columns...

Setup a set of column and/or row guides

Guide options...

Configure an individual guides. This option depends on the guide closest to the point where the mouse was clicked.

Snapping to guides

As a component or set of components is dragged about within the page designer the edges of the components will automatically snap to the guide whenever within range.

Guides will tend to cause a component to resize as a component is dragged but once a component goes out of range it will snap back to its original size.

Snapping to hints

In addition to the snapping of components to the guides the editor will show layout hints. These hints correspond to the edges of other components on the form. The hints therefore act as a form of automatic alignment of components. Later we plan to enhance this mechanism to support the alignment of baselines and the preferred platform component spacings (both features of the forthcoming JDK 6).

Setting up Columns

While in the configuration mode you can choose to setup columns and rows to assist layout. The GuideChooser dialog appears when this option is chosen.

You can use this dialog to quickly setup regularly spaced columns and rows. Once the guides have been added you may reposition the guides to best suit your layout needs.

A form will typically require a number of different guide types with some absolute position and some relative or scaled positioning.

Using guides

While it is possible to use guides purely for design time layout assistance the guides are of added benefit at runtime when a page is resized. To actually use the guides for this purpose it is necessary to use the GuideLayout manager. To use this Layout manager select the panel or container for which you wish to apply this option and set the layout property in the component's property sheet (normally to the bottom right of the page designer).

You may not see any immediate effect of choosing this layout manager but if you view the page's XML you will see that additional constraint attributes are added to the components belonging to the affected panel. The constraints refer to the guide ID which you may have noticed while setting up the guides. The guides are numbered from zero, up from left to right and from top to bottom. The constraint is thus just the list of guides to which the left, top, right and bottom edges of the component are bound (a -1 value indicates an unbound edge).

An example guide layout

To illustrate the use of the guide layout let's look at a sample form. The form is made up of two columns of labels and edits. Therefore a four column guide layout is created so that we can specify the spacing between the labels and edits and also the spacing between the columns, just as is shown above.

The leftmost guide in each column and the last guide (vertical guides 0, 3 and 7) are scaled guides so that each holds its position relative to the page. The next guide is a relative guide specifying a fixed width for the label. Following this another relative guide with a fixed width of 10 pixels is used to specify the spacing between label and edit. The edit field is then left to use the remaining space between the fixed guides and the scaled guides.

The XML code for the complete form layout is shown below.

Guide layout

  1. <Page Layout="Guide">
  2. <Layout/>
  3. <Guides width="640" height="480">
  4. <vert >
  5. <Guide id="0" pos="10.0" type="abs" coords="abs" />
  6. <Guide id="1" pos="150.0" type="rel" coords="abs" prev="0"}
  7. <Guide id="2" pos="10.0" type="rel" coords="abs" prev="1"}
  8. <Guide id="3" pos="49.063" type="abs" coords="rel"}
  9. <Guide id="4" pos="10.0" type="rel" coords="abs" prev="3"
  10. <Guide id="5" pos="150.0" type="rel" coords="abs" prev="4"
  11. <Guide id="6" pos="10.0" type="rel" coords="abs" prev="5"
  12. <Guide id="7" pos="98.125" type="abs" coords="rel"
  13. </vert >
  14.  
  15. <horz >
  16. <Guide id="0" pos="10.0" type="abs" coords="abs"}
  17. <Guide id="1" pos="20.0" type="rel" coords="abs" prev="0"}
  18. <Guide id="2" pos="10.0" type="rel" coords="abs" prev="1"}
  19. <Guide id="3" pos="20.0" type="rel" coords="abs" prev="2"}
  20. <Guide id="4" pos="10.0" type="rel" coords="abs" prev="3"}
  21. <Guide id="5" pos="20.0" type="rel" coords="abs" prev="4"}
  22. <Guide id="6" pos="10.0" type="rel" coords="abs" prev="5"}
  23. <Guide id="7" pos="20.0" type="rel" coords="abs" prev="6"}
  24. <Guide id="8" pos="30.0" type="rel" coords="abs" prev="7"}
  25. <Guide id="9" pos="97.917" type="abs" coords="rel"/>
  26. <horz >
  27. </Guides>
  28.  
  29. <Components>
  30. <!-- guide constraints: left, top, right, bottom -->
  31. <Label x="10" y="10" w="150" h="20" constraint="0,0,1,1" content="First Name"/>
  32. <Edit x="170" y="10" w="144" h="20" constraint="2,0,3,1"/>
  33. <Edit x="170" y="40" w="144" h="20" constraint="2,2,3,3"/>
  34. <Edit x="170" y="70" w="144" h="20" constraint="2,4,3,5"/>
  35. <Edit x="170" y="100" w="144" h="20" constraint="2,6,3,7"/>
  36. <Label x="10" y="40" w="150" h="20" constraint="0,2,1,3" content="Second Name"/>
  37.  
  38. <Label x="10" y="70" w="150" h="20" constraint="0,4,1,5" content="Phone"/>
  39. <Label x="10" y="100" w="150" h="20" constraint="0,6,1,7" content="Fax"/>
  40. <Edit x="485" y="11" w="144" h="19" constraint="6,0,7,1"/>
  41. <Edit x="484" y="40" w="144" h="21" constraint="6,2,7,3"/>
  42. <Edit x="484" y="69" w="146" h="22" constraint="6,4,7,5"/>
  43. <Label x="325" y="10" w="150" h="20" constraint="4,0,5,1" content="Address"/>
  44. <Label x="324" y="41" w="152" h="20" constraint="4,2,5,3" content="Street"/>
  45. <Label x="324" y="69" w="150" h="22" constraint="4,4,5,5" content="Town"/>
  46. </Components>
  47. </Page>

The constraints for each component specify the guide ID for the left, top, right and bottom edges. If the component is not bound to a guide then the ID can be specified as -1 and the x, y, w or h dimensions will be used to size and position the component as needed.

As of version 2.0.6 the guides can be stored in a separate file for reuse. Using this mechanism the guide files can be included using the following syntax:

Including guides

  1. <Guides include="guides/myGuideFile.xml">

By convention the guide files are included in the guides subfolder of the pages folder. The .xml extension is optional.

To see the guide layout in action we then resize the form. In the resized form each column still occupies half of the page although the relative size of the edit fields has increased.

Reusing guides

Sometimes, to promote consistency across forms you may wish to reuse a set of guides. So far we have no explicit support for such a process with Aria, however you can achieve the same effect by manually cutting and pasting the guide setup from one form to another.

The entire guide declaration is contained in the Guides element at the start of each page, with a Guide entry for each vertical and horizontal guide.

Layout managers

Java introduced the concept of layout managers as a way of managing cross platform layout issues. There are a wide variety of layout managers available within the core Java libraries and from third parties. However for reasons of simplicity only a core set of layout managers is supported within Aria.

Layout types

LayoutManager

Description

NULL

This really isn't a layout manager and instead components are positioned according to their absolute coordinates, specified at design time. This layout will not cause components to be repositioned as the container is resized.

Border

A built-in Java AWT and Swing layout manager dividing the container into five predefined areas. See the Java documentation for more details.

Flow

A built-in Java AWT and Swing layout manager. Components are arranged in the order in which they are added either as a row or column and depending on their preferred size. See the Java documentation for more details.

Card

A built-in Java AWT and Swing layout manager. Arranges the component in a stack and displays only the selected component so that it completely fills the container. The first component added to a CardLayout object is the visible component when the container is first displayed. See the Java documentation for more details.

Box

A built-in Java Swing layout manager. Similar to a FlowLayout, it arranges components along the X or Y axis. Provides slightly more control over the layout than the FlowLayout. See the Java documentation for more details.

Grid

A built-in Java AWT and Swing layout manager. Arranges components in a grid or table like structure. See the Java documentation for more details.

GridBag

A built-in Java AWT and Swing layout manager. Horribly complex to arrange and use. The GridBagLayout class is a flexible layout manager that aligns components vertically and horizontally, without requiring that the components be of the same size. Each GridBagLayout object maintains a dynamic, rectangular grid of cells, with each component occupying one or more cells, called its display area. See the Java documentation for more details.

Spring

A built-in Java AWT and Swing layout manager. See the Java documentation for more details.

Scale

An Aria layout manager that uses the absolute positions of the components and scales them in proportion to the container's runtime size. Optionally allows fonts to be scaled.

Guide

An extension of the ScaleLayout but using the Guides to add extra control over the scaling. The constraints for this layout are set automatically

Aria also includes a number of other layout managers used by some of its builders and wizards but not yet directly supported by the editor or by the basic Aria setup. These layout managers include:

Extra layouts

LayoutManager

Description

ColumnLayout

A columnar layout for use in forms or the like, where a set of components is associated with a set of labels or other such controls and laid out in columns.

The components are added in rows, but align to the columns. An example usage is in creating forms where a left hand column may contain labels or captions while the right hand column may contain the input fields. Indentation, spacing and padding may be added to control the layout and define the location of individual compoents.

LayerLayout

The LayerLayout is intented for use with pages and panels where you want to overlay one set of components with another. All the children of the container with the LayerLayout are given the same size and hence overlay one another. The components are located in the order in which they are created. An application may have to set the opaque property of the panels added to the layers so that the layers appear as expected.

The layering is intended to allow things like background decorations to be added and controlled easily. Layering may also be used to implement features such as overlays and modal/lock-out behavior, say for example overlaying a progress animation during a long running operation.

HtmlFormLayout

A layout mimicing the way in which HTML lays out its components

While these layout managers are not yet supported by the editor you can still make use of them by coding their use directly in Java. Some of the examples shipped with Aria make use of these layouts.

Component hierarchies

It is often a good idea to subdivide an application and its pages into different areas.

At the highest level and application may use the notion of a frameset to subdivide the screen or page. Typically a frameset is used where there are persistent elements to an application such as headers, footers or sidebars. Framesets are often used for navigation controls like toolbars, progress meters, structural views (tree views) and so on.

Within an individual page further nesting of content may be used with various types of panels, scrollpanes, splitters and tab controls.

Generally the more an application can subdivide the hierarchy of components the easier it is to control the layout of those controls.

The coordinates of child panels and of the components nested in a child panel are always relative to the parent or owner of the component or panel. The coordinates are also always measured from the top left of the panel that owns the component.

Using a layout manager

In using a layout manager in Aria there are two steps to consider. First the parent container for a component must specify the type of layout manager it employs and secondly each component should specify the constraints that the layout manager should use to lay it out.

Using XML the layout is added to the page or parent component as in the example below:

Sample Page using Layout Managers

  1. <Page class="org.formaria.testpilot.ApplicationRecorder" layout="border">
  2. <Components>
  3. <ScrollPane constraint="center">
  4. <Table2 name="table" horizontal_scrollbar="as needed"
  5. vertical_scrollbar="always"/>
  6. </ScrollPane>
  7. <Panel name="SouthPanel" constraint="south" layout="flow" style="base">
  8. <Button name="btnOption" content="Options"/>
  9. <Button name="btnStart" content="Start" enabled="false"/>
  10. <Button name="btnPause" content="Pause"/>
  11. <Button name="btnSave" content="Save" enabled="false"/>
  12. <Button name="btnBack" content="Back"/>
  13. <Button name="btnAssert" content="Assert" enabled="false"/>
  14. </Panel>
  15. </Components>
  16. <Events>
  17. <Event method="setOptions" target="btnOption" type="MouseHandler"/>
  18. <Event method="startScript" target="btnStart" type="MouseHandler"/>
  19. <Event method="addPause" target="btnPause" type="MouseHandler"/>
  20. <Event method="saveScript" target="btnSave" type="MouseHandler"/>
  21. <Event method="goBack" target="btnBack" type="MouseHandler"/>
  22. <Event method="addAssert" target="btnAssert" type="MouseHandler"/>
  23. </Events>
  24. </Page>

In the example the page is given a border layout and its children, the scroll pane and the panel are given constraints that apply to the border layout. The panel in turn is given a flow layout, but in the case of a flow layout the only constraint is the order in which items are added to the container.

The layout in individual components is controlled by the constraint attribute. The value of the constraint attribute varies according to the layout manager in use. The AriaEditor editor will present a list of the constraints that are appropriate for the particular layout manager.

Framesets

In earlier chapters (See Frames setup.) we saw how framesets could be added and configured. The frameset is an important layout device for applications in that different frame elements can have different lifecycles. Frequently the top, left and bottom elements in a frameset are unchanging within an application, and this long running context provides a space for common features such as toolbars, navigation bars, banners and feedback information.

The frameset is control by a border layout (see See Layout managers.) and you can update each named area of the frameset independently of one another. The frames or named areas within the frameset are termed targets within Aria. Using the targets is straightforward.

We have seen how a page can be displayed by calling the showPage(...) method:

Showing a single page

  1. // Via the page manager member variable of the page
  2. pageMgr.showPage( "DetailsPage" );

A second version of the showPage method takes an additional argument that specifies the target area:

Showing a single page in the content target area

  1. // The manager objects
  2. pageMgr.showPage( "DetailsPage", "content" );
  3. pageMgr.showPage( "StatusPage", "footer" );

The name of the target area matches what was specified in the frames file. After this what you do with the frames is really up to you. The framesets are just a way of dividing up the screen real estate and after that the pages they contain are treated as though a single page was being used with all the same lifecycle methods being called.

Editing framesets

Framesets when displayed comprise multiple pages and for a visual editor such as Aria this represents a number of issues and some of these issues impinge upon the operation of the page designer. The page designer can display a page within its frameset and this is normal behavior (in fact you are given a choice of using the frameset or not when opening a page).

You need to be aware that when editing a page within a frameset you are also editing the other pages referenced by the frameset. Normally the designer works with one page, one XML file and one Java source file for the page's response methods but in the case of a frameset other pages are also open. This is evident if you manipulate components on other pages within the frameset, you will see a small red icon appear next to the page files in the files view indicating that the file is modified.

The page you opened in the designer continues to be the one being edited and if you switch to XML it is this page's XML that you will see. However if the other pages are open in different instances of the page editor you can see their XML too.

Importantly the same instance of a frameset page is used in all instances of the designer. Therefore if you edit a page in one designer and switch to another that also shows the page in a frameset you should see your changes appear in the second designer.

Stopping home page from loading

In some applications the frameset may be modified or configured at startup, and in these cases it may be undesireable to have the framework load the home page into the frameset's content area. To stop the framework loading the home page in this way set the startup property StartClass to NONE:

Specifying that no home page should be loaded

  1. StartClass=NONE

When the start class is set in this way the content area is filled with the file/class specified by the frames.xml file. If the frames file does not specify the file then the content area will start in an empty state.

Application Styles

While Aria supports framesets within what would otherwise be considered an SDI (Single Document Interface) interface the framework supports a variety of other application styles, including the MDI (Multiple Document Interface) and a Docking Framework. The frameset is therefore used to describe the composition of the application and the `target' areas of the application. The target areas are those areas, panels or windows (depening upon the application style) that can be manipulated by the page manager and the showPage method. For more information on these application styles see See Application styles..

References

For more information on layout managers see the Sun's Java documentation.

http://java.sun.com/docs/books/tutorial/uiswing/layout/using.html

Data Binding

Data binding

Aria applications use the Model-View-Controller ( MVC) pattern to separate the key concerns in building an application. A key principal of this separation is that the model can be implemented without overdue concern for the presentation layer or user interface. Keeping the model simple makes it easy to implement business logic.

The separation of the model also means that the business logic need have little concern for the modalities or peculiarities of the user interface or the user interaction within the application.

Of course the model cannot be completely divorced from the user interface as occasionally interaction is required, say for instance if the model requires special input or needs to signal an error. Aria achieves the separation of concerns espoused by the MVC architecture by implementing a loose coupling between the data model and the user interface.

On the data or model side of the architecture Aria provides a rich data model that can be addressed via XML and XPath like references. This model can be composed of a wide variety of data and the data can be used in a variety of ways depending on the needs of the user interface and application.

Typically the data model will consist of data from a number of data sources such as static data defined in flat files, tables mapped in from a database, server side data obtained via service calls and configuration information.

Aria also saves its own data to the model, for example saving the component state data to the aria_state node so that things like list selections indices can be accessed via the mode or used for master details linking.

Your application may also save data to the model as it needs. The model grows with your application and its content is highly configurable. For example each node within the model can have multiple child nodes and each node can be a rich type, not just the basic types built into Java. Tables themselves are good examples of these composite objects, consisting of multiple rows and with rows consisting of multiple fields.

The diagram below shows how various data sources might be mapped into the hierarchy and how an individual object can be a composite of other objects. The diagram also serves to show the hierarchical arrangement of data which we will see being used in the next section on data binding.

Data binding

Aria uses a data binding technique that allows components to abstractly reference data in the model. This data can be simple scalar values or more complex types like lists and tables. From the UI perspective all that needs to be known is where the data resides in the model. Once the location has been declared Aria takes care of getting and saving the data via data binding objects which are implicitly constructed as your pages are loaded.

An example binding is shown below.

Simple data binding

  1. <Data></P>
  2. <P>
  3. <Bind name="ageCombo" source="customers/ageRanges"/></P>
  4. <P>
  5. <Bind name="firstNameEdit" source="firstname"/></P>
  6. <P>
  7. </Data>

In the above example two user interface components are bound to data. The components are named by the name attribute while the location of the data is referenced by the source attribute.

These bindings cause the named UI components to be filled with data from the source nodes whenever the page containing the components is displayed. In this specific example the ageCombo is filled with items from the customer/ageRanges node within the data model, while the firstnameEdit is filled with data from the firstname model node.

Even if the model hasn't been populated Aria will automatically create storage for the data. So, if for instance the user interface component is an input component like an edit field Aria will create a node in the model to hold the user input. In this way the model can be subsequently referenced and the user interface value can be retrieved without knowing how the input was obtained. Similarly if the bound storage is updated or modified then the user input field will display the new value whenever it is redisplayed. Furthermore, the same model node can be bound to multiple user interface components so that the same value can be displayed across multiple pages or in different contexts.

A simple static data binding

The simplest form of data that can be bound to the user interface is a static binding. A static binding is a binding that binds static or read-only data to a user interface component. Static data is often used for population of drop-down lists, labels and default values.

While it may seem like extra work to use data bindings for simple read-only data rather than hard coding the data it is often useful as the data is contained in a separate file from the source code. At its simplest this separation means that the application will be a little easier to maintain as updates to code are possible without risk to the application code (thus minimizing testing requirements).

At a more sophisticated level the separation can assist in branding of applications as the static data can be easily swapped for other data. Other features are enabled by the mapping of data used for static bindings including localization and customization of things like dimensional units, however these topics are beyond the scope of the current chapter and will be touched upon in later chapters.

Configuring the data source

So how does Aria get the data in the first place? The data source used by Aria is automatically created and configured whenever a new application is configured, but it is worth highlighting the various steps and processes used to locate the data.

Upon startup the startup.properties file of the application is accessed and the ModelData parameter is checked. This parameter points to the data model configuration file.

The data source configuration reference in the startup file

  1. ModelData=simpledatasets.xml

The configuration file is loaded by the application and processed as it contains a list of data sources. There may be one or more data sources depending on the needs of your application, but again this two stage configuration adds flexibility by allowing otherwise disparate data sources to be cleanly mapped into the data model. For examples, the specification required for database access differs considerably from the simple setup required for static data. The example below shows one such very simple configuration file.

The data source configuration file (simpledataset.xml)

  1. <?xml version="1.0" encoding="UTF-8"?></P>
  2. <P>
  3. <DataSources></P>
  4. <P>
  5. <DataSource name="SimpleData" filename="simpledata.xml"/></P>
  6. <P>
  7. </DataSources>

The file simply points of the another file which contains the static data. In a more complete application there may be several datasources of varying types and each would be listed in the data sources configuration file as a DataSource entry.

The actual data file is also relatively simple. The important attributes to note are the id attribute and the value attribute.

The static data file (simpledata.xml)

  1. <Datasets>
  2. <dataset id="customer">
  3. <data id="firstname" value="Joe"/>
  4. <data id="surname" value="Bloggs"/>
  5. <list id="ageRanges">
  6. <item value="18 - 25" id="1"/>
  7. <item value="26 - 32" id="2"/>
  8. <item value="32 - 45" id="3"/>
  9. <item value="46 - 65" id="4"/>
  10. <item value="65+" id="5"/>
  11. </list>
  12. </dataset>
  13. <dataset id="financial">
  14. <data id="carcost" value="10000.00"/>
  15. <data id="deposit" value="3000.00"/>
  16. </dataset>
  17. </Datasets>

The id field is important as it is this attribute that is used to identify a node in the model. The path used to access an individual node is just the list of all the node id s back to the root node separated by the / character. This concept should be familiar to anyone who has used a filesystem being vary like the paths that operating systems use.

As mentioned above, these configuration files are setup by Aria and Aria also provides editing facilities so you will rarely need to interact with the files directly.

The files and paths to the configuration files can also be chosen as part of the project settings.

How the bindings work

The data bindings work on the simple principle that any change made by the user to the user interface component will cause the associated model node to be updated and made consistent.

Similarly any change to the model will be propagated back to any user interface components that are bound to the model node. Aria uses some extra steps to eliminate unnecessary work and unnecessary updates but the key update occurs during page transitions and these are discussed in a later section (See Update on page display.).

When the model is updated one possibility would be to dynamically update the user interface to reflect the latest data, however this would quickly get out of hand if multiple nodes were updated. Since there is no obvious point at which we can say that an internal (to the application) is complete the update from the model side is not as aggressive as the saving of user interface data and more is left to the discretion of the programmer.

From the user interface side the bindings listen for any user input or change in state. Each binding is aware of the type of input events that affect its data and add the appropriate listeners. As these listeners are triggered they save the data to the model.

To accommodate the differences between user interface components and the types of updates they require Aria includes a range of binding types and adapters that can act as intermediaries between bindings and the various types of model node. All of these bindings share a common superclass and form a hierarchy.

Aria constructs the appropriate bindings by mapping the model data node or data source to the target user interface component. To reiterate the way in which bindings are setup In XML we again show a binding for an edit field:

A simple data binding for an edit field

  1. <Bind target="nameEdit" source="persone/name"/>

For more complex data types a one to one relationship may not exist between the data type and the user interface component. Indeed there may not be a single data binding type that can perform such binding so Aria allows chaining of data bindings and the use of adapters. For example, in the case of a type such as a table it would not be realistic to try and display a table in an edit field yet we might want to display a particular field value. With the aforementioned chaining Aria can handle such situations and fortunately it can even set up these bindings for you automatically.

The point of all of this is that in using bindings you are not constrained to using simple types or simple relationships, you can build and use a wide variety of data.

Separating data from state

We started by considering the binding of static data as read-only data. Since the data source is read-only it is not possible to save the modified data to the same node and therefore Aria creates a new node for the data by appending the source path to the ` aria_state ' node. Thus where the above example sourced its data from the ` person/name ' node the result is saved to ` aria_state/person/name '.

Some controls such as drop-down lists produce selection information (such as the index of the selected item) that do not fit well with the input data (the items it the list), particularly if the input data is considered static or read-only. Again this data is saved to the ` aria_state ' node.

For the most part you need not be aware of the output node as it is an internal detail of the bindings. In the above example the output path was not specified as Aria automatically configures the path by appending the source path to the aria_state node to give an output path of aria_state/person/name in the case of the last example.

Normally the output values and state information (e.g. the selection index in a list) associated with a data bound component are saved to the aria_state node. The output path can be specified explicitly in the binding or Aria can create it automatically by appending the source path to the path /aria_state , note however that this does not occur if the source path begins with a forward slash ( / ). Thus in the example above if the source path had been `/ person/name ', instead of ` person/name ', then the output path would be ` /person/name ' and the binding would save the user data back to the same node in the model.

Setting up a binding

Setting up a binding in XML is pretty straightforward as has been shown above. The bindings are specified in the Data section of the page:

Some more bindings

  1. <Page>
  2. ...
  3. <Data>
  4. <Bind target="firstNameEdit" source="person/firstName"/>
  5. <Bind target="secondNameEdit" source="peson/secondName"/>
  6. </Data>
  7. </Page>

While Aria setups the appropriate binding if a page is constructed from XML the story in Java is a little more complicated as the type of binding must be chosen explicitly:

Adding a binding in Java

  1. addBinding( new TextBinding( firstNameEdit, "person/firstName" ));
  2. addBinding( new TextBinding( secondNameEdit, "person/secondName" ));

In this example we know that the bound user-interface components are edit fields and therefore we use the TextBinding binding type. Some of the bindings provided by Aria are:

Some data binding types

Binding

Usage

LabelBinding

A simple binding that is used for read-only Label components

TextBinding

Bind a TextComponent to a data model value/node. The binding allows a model node to linked to a UI component so that it can be refreshed when new data is written to the model or conversely when the UI component needs to write data to the model.

This binding is designed to be used by components such as Edit fields, TextComponents or TextFields

ListBinding

Bind a list to a data model value/node. The binding allows a list model node to linked to a UI component so that it can be refreshed when new data is written to the model or conversely when the UI component needs to write data to the model.

This binding is designed to be used by list like components such as comboboxes or drop down lists.

StateBinding

Bind a component's state to a data model value/node. The binding allows a model node to linked to a UI component so that it can be refreshed when new data is written to the model or conversely when the UI component needs to write data to the model.

This binding is designed to be used by components such as Checkboxes.

This state change does not affect the content displayed by the component

Happily once the bindings have been constructed there is no difference between bindings constructed via Java or via XML.

Aria contains other binding types some of which support features such as localization of lists, conversion of physical dimensions and filtering of data. Please refer to the API documentation for further details.

Update on page display

Aria tries to ensure that the data visible on screen is always consistent with what is stored in the model. One of the most important times for this is during page transition. When a page is shown, its data must be updated and equally if any page had been visible its data must also be saved.

When switching pages Aria goes through a number of steps to ensure that the displayed data is consistent with the model state. The steps are as follows:

  1. The current page's data is saved by calling saveBoundComponentValues ().
  2. The current page is marked as deactivated and pageDeactivated () is called.
  3. The new page is added to the target container.
  4. The new page's bindings are updated by calling updateBindings . This method will evaluate each binding's source and output attributes and invoke any callback methods. This gives the page the opportunity to modify its bindings prior to display.
  5. The new page's values are updated by invoking the updateBoundComponentValues method
  6. The new page is marked as activated and pageActivated () is called.

You can interact with the update by implementing the pageActivated() and pageDeactivated() methods, or you can invoke the other methods to update the model at any point in the life of the application. In most cases however the data bindings take care of all the work needed to ensure the displayed data is up to date.

Saving values

As mentioned above a page's data is saved upon page transitions but this is not always sufficient and it may be necessary to explicitly save the data at some point. This can be accomplished by calling the page's saveBoundComponentValues () method. This method iterates all the data bindings on a page and updates the associated model nodes.

Updating values

Just as you may want explicit control of saving you may want to force updates to a page's data. Updates can be forced via the updateBoundComponentValues () method. This method can be invoked at just about any time and you may need to do so anytime you have a calculation of a piece of business logic that writes (or loads) data to the model.

Source and output nodes

Each data binding has a data source and optionally an output node. The role of the source node is to provide the data to be displayed by the bound component whereas the output node provides a place to save the user value or selection state.

In some cases it would not be desirable to save a user value to the same node as the input as this would either destroy the original value or modify the input dataset.

By default the output node defaults to the source node such that its path is ' aria_state/ '. Furthermore all output nodes are appended to the ' aria_state ' node.

The distinction between source and output nodes is a little gray. In some cases like a read only list it would not make sense to write selection information back to the list source so clearly in this the source and output should be maintained separate. In the case of an edit field things may not be so clear as the selection state (the caret position) is rarely of interest and one would therefore expect the input and output nodes to be the same. Aria can handle both these situations but ultimately you remain in control and can override the default behavior by explicitly naming the nodes.

Callbacks

Consider for a moment the case of reusable forms, say for example the case of a contact details form contain names, addresses and phone numbers. Such a form is pretty simple and from the above documentation you should be able to setup bindings for such a form without too much difficulty.

However, things start to get a little more interesting if the form is reused (as is often the case for something as ubiquitous as an address form). If say in the case of a financial application you had joint application for a mortgage then each person would have to fill out and address form. Now we could accomplish this by duplicating the form or by showing the form, capturing the data and then moving it to the right place but it would be a lot of work for little gain. In Aria there is another approach.

Aria supports dynamic bindings that can be updated during the life of an application. At the heart of a dynamic binding is the callback. The syntax for the callbacks is:

An embedded callback method

  1. source="${myMethod(args)}"

where myMethod is the name of a public method in the page's class. The method can be argumentless or it can have String or integer arguments. The path can also contain multiple expressions and fixed elements, for example

A callback substituting a path element

  1. source="users/${getCurrentUser()}/firstName"

where getUserName evaluates to some sort of user ID that exists (or will exist) in the model. The callback syntax is loosely based on expression language syntax and is explained more fully in See Evaluated attributes and helpers..

Dynamic bindings

Using the above technique it is possible to do things like switching users when showing user details on a page. All that needs to be done is have the getCurrentUser return a different ID and invoke the updateBindings method (which is invoked implicitly during page transitions anyhow).

Thus, as you process one applicant for the aforementioned joint mortgage application you can set the appropriate customer/user ID and then as the application process progresses you can begin the data capture process for the second user by updating the ID and redisplaying the form. Since the evaluated path has changed by the time the form is redisplayed the components on the form will be bound to different locations.

Adapters

Sometimes it is not possible to have a direct correlation between the model's data structure and a binding's use of that data. In such cases an intermediate adaptor is used. Normally the data factory takes care of the instantiation of adapters, but in some circumstances, for example when coding a specific feature in Java, it may be necessary to construct an adaptor.

The role of the adaptor is to allow the specification of bindings so that only the target component and the data source need be specified, i.e. the endpoints. The binding factory can then take care of all the rest.

Adapters are frequently used where a complex model node such as a table or list node are being mapped to a simple output type like an edit field. In most cases you need not be aware of the adapter's role in a binding but in some circumstances you may want to modify some of the adapter's properties. To do this you must use Java as the XML interface does not support such properties.

Binding tables

Using a databse table in a UI component such as a JTable is straightforward, here are some examples:

Some sample table bindings

  1. <Bind target="nameEdit" source="Voltages2" output="tables/editValue"/>
  2. <Bind target="myList" source="Voltages" output="tables/listValue" display="1" />
  3. <Bind target="myTable" source="Voltages2" output="tables/tableValue"/>

For now the output values can be ignored, they are used to save the state data of the bindings (the index of the selected record).

Selecting tables

For more complex queries it is possible to dynamically query a database, but to do this you need to use Java code and setup both the model node and the binding yourself.

A new database table can be configured as follows:

Select DISTINCT values from a table

  1. DatabaseTableModel completeVoltageTable = DatabaseTableModel.getTable( "Voltages" );
  2. completeVoltageTable.setDistinct( true );
  3. completeVoltageTable.setOrderField( "ID" );
  4. completeVoltageTable.retrieve();

Here the code starts by retrieving the basic ' Voltages ' table and then sets some additional attributes to order the table by the ' ID ' field and sets it to retrieve only the distinct rows.

The data is not pulled from the database till the retrieve method is called. It is worth noting that of course once the node is in the model you do not need to repeat the process and can instead just bind components to the node as needed.

Sometimes it is not desirable to predefine a table and in such cases a completely new database model node can be prepared in Java code.

Setup a database query

  1. DatabaseTableModel voltageTable = new DatabaseTableModel();
  2.  
  3. // Set the query elements
  4. // FROM clause, FIELDS, WHERE clause
  5. voltageTable.setupTable( "CS_VOLTAGES",
  6. "VOLTAGE_DESCRIPTION, VOLTAGE_MIN, VOLTAGE_MAX",
  7. "FREQUENCY=50" );
  8. voltageTable.retrieve();

The fields to retrieve and the where clause are specified with this approach.

As an alternative the complete SQL statement can also be specified using the setSqlStatement method.

Once the tables has been retrieved it can be used in a UI component by updating the component's bindings

Set and refresh a table control

  1. myTable.setModel( voltageTable );
  2. updateBoundComponentValues();

Linking components

To build a form using multiple UI components bound to a database table we can use the output attribute of the bindings. The output attribute is used to save the state data of the bindings.

For components such as list bindings these attributes include the selected item in the list. When displaying a list the binding will try to have the list display the last selected item (the value pointed to by the output node).

Tables and other components that use the database bindings similarly save their state to the output node. The table binding in particular saves (by default) the current row index of the table. Therefore by changing the selected row on a bound table the selected row on the underlying database table is updated. Then if the UI components bind to the same output path they should all refer to the same table row.

An example of this is the page described below:

A complete example of table usage

  1. <Page class="aria.projects.sqltables.MyTable" layout="border">
  2. <Components>
  3. <Label name="title" content="XTable Demo" alignment="center"
  4. constraint="north" style="Heading"/>
  5. <TabPanel constraint="center" >
  6. <Table name="myTable" title="first" interactive="true"
  7. headingStyle="TableHeading" style="TableData"
  8. selectionStyle="TableSelection" horizontal_scrollbar="as needed"/>
  9. <ScrollPane title="myTable2">
  10. <Table2 name="yourTable" title="second" interactive="true"
  11. headingStyle="TableHeading"
  12. style="TableData" selectionStyle="TableSelection"
  13. horizontal_scrollbar="as needed" updateModel="true"/>
  14. </ScrollPane>
  15. <Panel layout="border" title="myTables">
  16. <Combo name="myList" constraint="north" x="10" y="10" w="100" h="50"/>
  17. </Panel></P>
  18. </TD>
  19. </TR>
  20. <TR>
  21. <TD ROWSPAN="1" COLSPAN="1">
  22. <P>
  23. <Panel layout="border" title="myForm">
  24. <ScrollPane title="myTable3" constraint="center">
  25. <Table2 name="ourTable" title="second" interactive="true"
  26. headingStyle="TableHeading" style="TableData"
  27. selectionStyle="TableSelection"
  28. horizontal_scrollbar="as needed" updateModel="true"/>
  29. </ScrollPane>
  30. <Panel layout="border" title="myTables" constraint="south">
  31. <Edit name="voltageEdit" constraint="west"/>
  32. <Combo name="voltageList" constraint="center"/>
  33. </Panel>
  34. </Panel>
  35. </TabPanel></P>
  36. </TD>
  37. </TR>
  38. <TR>
  39. <TD ROWSPAN="1" COLSPAN="1">
  40. <P>
  41. <Panel layout="grid" constraint="south" rows="1" hgap="2" vgap="2" border="1">
  42. <Edit name="nameEdit"/>
  43. <Button name="prevBtn" content="prev" />
  44. <Button name="nextBtn" content="next" />
  45. <Button name="sortBtn" content="sort" />
  46. <Button name="filterBtn" content="filter" />
  47. </Panel>
  48. </Components></P>
  49. </TD>
  50. </TR>
  51. <TR>
  52. <TD ROWSPAN="1" COLSPAN="1">
  53. <P>
  54. <Events>
  55. <Event method="syncMouseSelection" target="myTable" type="MouseHandler"/>
  56. <Event method="next" target="nextBtn" type="ActionHandler"/>
  57. <Event method="prev" target="prevBtn" type="ActionHandler"/>
  58. <Event method="sort" target="sortBtn" type="ActionHandler"/>
  59. <Event method="filter" target="filterBtn" type="ActionHandler"/>
  60. <Event method="myListKey" target="myList" type="ItemHandler"/>
  61. <Event method="updateBoundComponentValues" target="ourTable"
  62. type="ListSelection"/>
  63. </Events></P>
  64. </TD>
  65. </TR>
  66. <TR>
  67. <TD ROWSPAN="1" COLSPAN="1">
  68. <P>
  69. <Data>
  70. <Bind target="nameEdit" source="Voltages2" output="tables/Voltages/>
  71. <Bind target="myList" source="Voltages" output="tables/Voltages" display="1" />
  72. <Bind target="myTable" source="Voltages2" output="tables/Voltages/mt"/>
  73. <Bind target="yourTable" source="Voltages" output="tables/Voltages/yt"/>
  74. <Bind target="ourTable" source="Voltages" output="tables/ot"/>
  75. <Bind target="voltageEdit" source="Voltages" output="tables/ot"/>
  76. <Bind target="voltageList" source="Voltages" output="tables/ot" display="1" />
  77. </Data>
  78. </Page>

In the above example three tabs are shown, on the third a table an edit field and a drop down list are all bound to the same table node and each outputs to the same path. Changing the selection on the table causes the selection on the edit field and combo box to be updated.

This update occurs because the table handles the ' ListSelection ' event and in doing so causes the ' updateBoundComponentValues ' method to be called. This method updates all the UI components bound to the model.

The output attribute specifies the path within the model to which the selection attributes are saved. These attributes include the row selection index of a table control. Normally the output of a Aria model is saved to a specific subpath in the model, the ' aria_state ' node and whenever an output path is specified it is automatically appended to this node.

In some cases it is desirable to refer to another source for this selection state. By specifying the absolute path within the model it is possible to address such paths rather than just the children of the ' aria_state ' node. In the case of a master-child setup it would be possible to link table selections using such a technique, the child table's output would be set to the master table's source path in such a scenario, e.g.

Example linking of bindings

  1. <Bind target="masterTable" source="Region" output="myRegionSelection"/>
  2. <Bind target="childTable" source="Voltages" output="/Region"/>

Finally, Aria as a Java based system is by default case sensitive. This case sensitivity also applies to database look-ups. SQL in contrast can be configured to be case in-sensitive. To help support this we allow look-up of fields in both case sensitive and case in-sensitive modes. The case sensitivity is set with a startup parameter in the startup.properties file

Flag case sensitivity

  1. CaseSensitiveDatabase=false

Advanced attribute evaluation and libraries

One of the goals Aria is to help promote an MVC architecture. The Data Binding and Event Binding helps make this clean separation by putting the UI declaration in XML, separate from the business logic which is implemented in Java. One limitation of this mechanism is that the custom logic had to be routed through event handlers in classes derived from the Page component.

This dependency on a UI component was undesirable in some cases and made it a little more difficult to implement libraries of reusable functions than we would have liked. So, as of version 2.0 we have extended the attribute and event bindings to solve this problem.

Evaluated attributes

Attributes within a Aria page can be specified dynamically, for example

Basic attribute evaluation

  1. <Button name="DecBtn" x="178" y="43" w="42" h="20" content="${getContent()}"/>

The code ${getContent()} is an expression that is evaluated at runtime each time the expression is encountered. For a page component declaration the expression is evaluated when the page is loaded but expressions can be used in other locations such as within the data model, the data bindings, the validations or the event bindings.

An evaluated attribute's implementing method is by default in the owner page such that a reference like ${myMethod()}, which would evaluate to a method in the current page with a signature like:

Method signature

public void myMethod();

In Aria 2.0 The attributes can also be defined in classes other than the current page or classes derived from Page. The syntax for such expressions is as follows:

Extended attribute declarations

Syntax

Behavior

${mypackage.MyClass.myMethod(args...)}

to invoke a static method

${mypackage.MyClass[].myMethod(args...)}

to create a new instance of the class on each evaluation

${mypackage.MyClass[referenceName].myMethod(args...)}

for a named object instance

${myMethod[referenceName](args...)}

for a method contained within the invoking page

${[referenceName].myMethod(args...)}

for a method contained within the class instance referred to by the reference name.

where mypackage is the name of the Java package containing the class MyClass . The value of referenceName is a user defined value that identifies the instance of the class. The application instantiates an instance of the class when the expression is first encountered and thereafter maintains the instance with each subsequent call retrieving the same instance of the class. As in early versions, the method call can also contain zero or more arguments.

What this means in practice is that the class or classes implementing an applications business logic no longer need be derived from Page. In this way it is possible to build libraries of reusable functions. Lets look at an example:

Sample extended attributes

  1. <Page class="com.mypackage.ui.swing.SimpleCalculator">
  2. <Components>
  3. <Edit name="Counter" ... />
  4. <Panel ...>
  5. <Button name="DecBtn" ... content="${org.formaria.library.Calculator.getDecLabel()}"/>
  6. <Button name="ClearBtn" content="${org.formaria.library.Calculator[foo].getClearLabel()}"/>
  7. <Button name="IncBtn" ... content="${[foo].getIncLabel()}"/>
  8. <Button name="divideBtn" ... content="/"/>
  9. <Button name="multiplyBtn" ... content="X"/>
  10. </Panel>
  11. </Components>
  12. <Events>
  13. <Event method="doEquals" target="ClearBtn" type="ActionHandler"/>
  14. <Event method="org.formaria.library.Calculator.increment"
  15. target="IncBtn" type="ActionHandler"/>
  16. <Event method="org.formaria.library.Calculator[].decrement"
  17. target="DecBtn" type="ActionHandler"/>
  18. <Event method="org.formaria.library.Calculator[foo].divide"
  19. target="divideBtn" type="ActionHandler"/>
  20. <Event method="[foo].multiply" target="multiplyBtn" type="ActionHandler"/>
  21. </Events>
  22. </Page>

In the above example we are recreating the simple calculator that we have used in other examples. While the example is a little contrived (we will document some more realistic examples later) it shows the new syntax in action.

Line 5: Shows something similar to the pre Aria 2.0 syntax (which is still valid) except that the handler method is now in a separate class ( org.formaria.library.Calculator ) and in a separate package to the page ( com.mypackage.ui.swing.SimpleCalculator ) class. The method being invoked is a static member of the class.

Line 6: Shows a similar call except that instead of a call to a static method a concrete instance of the class is constructed. Once the instance of the class is constructed it is labelled for subsequent use.

Line 7: Reuses the reference setup it line 6 and invokes a different method on the same object. The reference is project wide so the object associated with the name can also be used across pages.

Expression evaluators

The expressions are evaluated by an ExpressionEvaluator and each page has by default its own instance of the default expression evaluator. (The default evaluator delegates storage of the referenced classes to the project). However, the page allows this evaluator to be replaced and a different evaluator can be inserted. This replaceable evaluator allows a route to include other expression evaluators such as interpreters. As an example an evaluator for the Groovy language has been created.

Using evaluated attributes

OK, so we have seen how the attributes of an XML file can in fact be callbacks to methods in your page class or in some other class. What does this mean for the application?

Essentially this means that the model is dynamic, it can be adapted to meet the changing needs of your application as a session progresses. You are not restricted to the setup encoded in the XML at start-up.

The dynamic model also means the data structure specified in the XML (or in your code for that matter) can be mapped from one instance to another via the evaluated attributes and callbacks. We have already seen how this could be use with something as simple as an address form.

Evaluated attributes also make it possible to apply more advanced techniques like filtering data and providing access control. Once you get to grips with the basic functionality you should find the use of evaluated attributes a very powerful mechanism.

Registration of custom data bindings

Since Aria supports an open ended number of compenent types and a large variety of data objects it is often useful to create reusable data bindings for these situations. Prior to version 3.0 it was possible to add custom data bindings, but the mechanism employed suffered from some limitations. Therefore, to deal with these limitations and to provide a more consistent regsitration mechanism the data bindings registration mechanism has been completely revised.

Adding a new binding type

The data binding mechanism in Aria shares some common features with the other configuration file systems in Aria and Aria. The system allows multiple configurations to be added to an application so that various modules can contribute support to an application, and in turn the factory classes iterate the configurations till suitable matching resources are found.

The matching strategies used with the configuration files allow various levels of refinement so that a broad concept like an interface can be matched or a very specific class instance can also be matched.

Adding a configuration file

The first step in employing the data bindings is to add a registration file for the bindings. By default Aria includes and adds a bindings.xml file for the built in bindings, and it is instructive to look at this file:

The built-in binding registration

  1. <Bindings>
  2. <InspectorBindings>
  3. </InspectorBindings>
  4.  
  5. <ClassBindings>
  6. <Binding target="org.formaria.*.Button"
  7. class="org.formaria.aria.data.TextBinding" type=""/>
  8. <Binding target="org.formaria.swing.RadioButton"
  9. class="org.formaria.aria.data.RadioBinding" type=""/>
  10. <Binding target="org.formaria.swing.RadioButton"
  11. class="org.formaria.aria.data.TextBinding" type="text"/>
  12. <Binding target="org.formaria.swing.RadioButton"
  13. class="org.formaria.aria.data.StateBinding" type="state"/></P>
  14.  
  15. <Binding target="org.formaria.swing.Checkbox"
  16. class="org.formaria.aria.data.StateBinding" type=""/>
  17. <Binding target="org.formaria.swing.Checkbox"
  18. class="org.formaria.aria.data.TextBinding" type="text"/>
  19. <Binding target="org.formaria.swing.Tree"
  20. class="org.formaria.swing.tree.TreeBinding" type=""/>
  21.  
  22. <!-- TODO add implementation of this error handling mechanism-->
  23. <!-- Binding target="org.formaria.*.Table">
  24. <onError fixError="DataSourceClassMissing"/>
  25. </Binding-->
  26. </ClassBindings></P>
  27.  
  28. <InterfaceBindings>
  29. <Binding target="org.formaria.aria.ListHolder"
  30. class="org.formaria.aria.data.ListBinding" type=""/>
  31. <Binding target="org.formaria.aria.ListHolder"
  32. class="org.formaria.aria.data.ListBinding" type=""/>
  33. <Binding target="org.formaria.aria.TextHolder"
  34. class="org.formaria.aria.data.TextBinding" type=""/>
  35.  
  36. <!-- TODO add implementation of this error handling mechanism-->
  37. <!-- This does not actually use a binding -->
  38. <!--Binding target="org.formaria.aria.ModelHolder">
  39. <onError fixError="DataSourceClassMissing"/>
  40. </Binding-->
  41. </InterfaceBindings>
  42.  
  43. <InstanceBindings>
  44. </InstanceBindings>
  45. </Bindings>

An application can add an application specific binding by including a bindings.xml file in its classpath. The name of the application bindings file can be changed by specifying the BindingsRegistry startup parameter.

An individual module or application can also add as many configuration files as it needs. For the data bindings the configurations are added to the RegisteredDataBindingFactory via the addConfigFile method.

The configuration file format

The matching strategies used with the configuration files allow various levels of refinement so that a broad concept like an interface can be matched or a very specific class instance can also be matched. The matching modes are as follows

 

 

 

InspectorBindings

Uses a custom/user defined Inspector class to identify the appropriate binding. The inspector must implement the Inspector interface via the inspector attribute.

InstanceBindings

Match a class to an instance of the specified class

InterfaceBindings

Matches to a class that implements the specified interface.

ClassBinding

Match a binding target to a instance of the specified class, or a subclass of the specified class.

Each of these binding types should specify the target, class and type attributes, but the registration may also specify addition attributes and these are available to the data binding once the XDataBinding.setup method is invoked.

Within a particular mode of bindings the bindings registrations are checked in the order in which they are registered and specified.

Specifying an InspectorBinding

As mentioned above the Inspector Bindings allow you to create bindings that can inspect the comtents and type of the source data and the type and instance of the target component. Using a special Inspector interface you can create your own inspector and thereby gain fine grained control of how a binding is used and applied.

The Inspector interface

  1. /**
  2.   * Called by the registration factory for InspectorRegistration instances
  3.   * to allow this matcher to arbitarate as to whether or not the component
  4.   * can be used with the particular registration object.
  5.   * @param comp the component involved in the drag oepration
  6.   * @param regConfig a table of registration attributes
  7.   * @param instConfig a table of attributes used in the instance
  8.   * declaration
  9.   * @return true if the handler matches, otherwise false
  10.   */
  11. public boolean inspect( Object comp, Hashtable regConfig,
  12. Hashtable instConfig );

Most bindings won't do very much with the parameters and instead rely on the setupHelper method to set the source and output data models to their initial state. An example of this is the TextBinding

TextBinding setup

  1. public void setup( Project project,
  2. Object c,
  3. Hashtable bindingConfig,
  4. Hashtable instanceConfig )
  5. {
  6. setupHelper( project, c, bindingConfig, instanceConfig );
  7. attribStr = (String)instanceConfig.get( "attrib" );
  8. }

Data binding adapter

Many data models nodes are not immediately usable in a binding, lacking the the form, structure, or the interface needed by the binding. An example of this is the ListBinding which uses the ListModelAdapter to adapt data source such as tables to lists. A list may display just one column of a table or a single attribute of a model node and the adapter adapts the model node to the requirements of user component. Various adapters are built into the Aria framework. The adapter is specified with the 'adapter' attribute of the binding instance for those bidning types that use adapters.

Data binding lifecycle

Once the data binding registration has been matched, an instance of that binding is created and the setup method is called is as described above. The setup method usually establishes all the data and resources need by the binding.

Data binding contexts and containers

The binding is maintained within the DataBindingContext owned by the page. As the page state changes with page navigation the data binding context requests that the binding saves its data by calling the set method and when the page is displayed the context calls the get method, requesting that the binding updates the UI component.

The application may also explicitly update the individual bindings using the updateBinding method, or via the updateBindings or updateBoundComponentValues methods.

Reevaluation of registered binding types

The updateBindings method differs from the updateBoundComponentValues method in that it causes a reevaluation of the binding source and output model specification. The reevaluation is often used if the model node specified hass changed, or if the binding paths include evaluated attributes.

Evaluated attributes are references to methods of the owning page (or sometimes library objects), that allow applications to control and manipulate the model paths used by the bindings. In this way the application can redirect the bindings to different parts of the model. Sometimes this redirection is used to do thing like adapting a page to the data of one object or another, for example the data belonging to one user or another.

One important aspect of the registration facility is that the basic data bindings is that the bindings do not store the tables of binding instance and binding registration attributes, so if these attributes are needed by a binding then it is the responsibility of the individual binding class to store the necessary data.

Using the ModelHelper

The ModelHelper class provides a number of methods designed to facilitate access to the data model and avoid the verbose type casting that is sometimes required.

The class can be used with a specific instance of a model node by instantiating a new instance of the helper, passing the target node as an argument to the constructor. However the most common use is via the static methods, for example:

Sample usage of the ModelHelper

  1. // Find a node and get its value
  2. String myValue = ModelHelper.getString( project, rootModel, "as/you/were" );
  3.  
  4. // Get an integer value from a specifc node
  5. int size = ModelHelper.getIntValue( modelNodes[ i ], "size" );</P>
  6. // Get a double or a default
  7. double velocity = ModelHelper.getDouble( carModel, "trabant/speed", 30.0 );
  8. double velocity = ModelHelper.getDouble( carModel, "ferrari/speed", 220.0 );

The ModelHelper is just one helper class in the Aria and Aria frameworks. If you see things like lots of casting or repetitive code look for a helper, there may already be one included in the framework or on the Aria forums. If you don't find what you are looking for write your own, they are not complicated and can save you lots of tedious casting!

Event Handling

Aria aims to simplify application development by reducing the amount of boilerplate code that must be generated for an application. One of the most time consuming activities and a troublesome feature of application development is the wiring together of components and events.

Java's abstract event handling mechanism is certainly flexible but more often than not the code needed for even simple event handling is repetitive and adds little of particular value to the application. In Aria the most common instances of this event handling wiring is taken care of by basic library functions so that all the Aria user need do is specify what is specific to a particular event.

Aria's event handling relies on the basic Java event handling mechanisms and at any point you can go beyond the Aria library and work directly with the low level event mechanisms. This chapter shows how to work with both mechanisms.

Adding EventHandlers interactively

Adding event handling in Aria is very easy. Simply select the component whose event you want to handle within the page designer. Then in the properties palette locate the event of interest. In the screen shot below you can see a mouse event being entered for the selected button.

Once the name has been entered and you press enter Aria finds the event handler in the source code or adds a new event handler if the handler doesn't exist already.

To correctly add the handler Aria must do a few extra things that may require additional input or modify the source code in other ways. Of these tasks the most obvious occurs if no name has been given to the component being edited then a popup dialog will appear requesting that you enter a name for the component.

Once a name is available for the component Aria can proceed and opens the source code in a text editor at the new event handler method.

If necessary a new source file may have been created into which the handler is added. The event is bound to the user interface and the new Java class by a declaration in the page's XML. You can see the event declaration by switching to the page designer and selecting the XML button in the designer's toolbar..

The page XML is updated as soon as the new method is added and the XML text editor is displayed.

And that's it, all of the infrastructure for event handling is taken care of and you can begin entering the application specific business logic.

Basic event handling

Regardless of how you edit the event handlers for your application the basic mechanisms are the same. The page's XML declaration names the event to be used to respond to a particular event type for a given component.

While the XML could point to a method in the page's base class or some existing utility class, most pages involve some form of custom class for handling the custom logic that belongs to the page. Pages can derive directly from Page but by changing the base class you can customize the event handling for components

This example re-introduces some Java code. Now the base class is our own custom written class derived from Page so that calls specified in the XML can be intercepted by the custom class.

A new Java class declaration

  1. public class EventHandling extends Page

The XML file now specifies this custom class as it's base class and defines the two buttons which will have events attached.

A page specifying events

  1. <Page class="org.formaria.samples.xmlbuild.EventHandling">
  2. <Components>
  3. <Component name="btnProceed" type="XBUTTON" x="40" y="10" w="60" h="20" content="Proceed"/>
  4. <Component name="btnCancel" type="XBUTTON" x="110" y="10" w="60" h="20" content="Proceed"/>
  5. </Components>
  6.  
  7. <Events>
  8. <Event method="nextPage" target="btnProceed" type="MouseHandler" next="xmlnavsamplescr2"/>
  9. <Event method="cancel" target="btnCancel" type="MouseHandler"/>
  10. </Events>

So now the java code handling the events is as follows:

The Java class's implementation of the events

  1. public void nextPage()
  2. {
  3. if ( wasMouseClicked() ){
  4. // Do something here...
  5. PageManager.showPage( getAttribute( "next" ));
  6. }
  7. }
  8.  
  9. public void cancel()
  10. {
  11. if ( wasMouseClicked() ){
  12. Button button = (Button)findComponent( "btnCancel" );
  13. button.setText( "Clicked!" );
  14. }
  15. }

Built-in event handlers

Aria supports several basic types event, the support types are described below:

Various event handler types

Type

Usage

ActionHandler

Action handlers respond to ActionEvents such as button clicks, selections, edit completions and so on. This is a simple interface with just a single event.

MouseHandler

Mouse events comprise an number of events including mouse entry, exit and changes of state including when the mouse is pressed, released and clicked. It is normal to check the type of event that trigger the call to a mouse handler method unless you have some generic action for all the above events.

MouseMotionHandler

Like the above mouse handler the mouse motion handler responds to more than one mouse event, namely the mouse moved and mouse dragged events. It is unusual to interact with this type of handler.

ItemHandler

This type of handler is invoked whenever the state of a component changes, for example the selection state of a radio button or a check box.

KeyHandler

This type of handler is used for handling keyboard event including key pressed, released and typed. Sometimes this type of handler is used to help enforce validation rules on input fields.

FocusHandler

A change of focus will trigger this handler both when a component gains focus and when it looses focus.

MenuHandler

Invoked whenever a menu item is selected.

TextHandler

Invoked whenever a text value has changed.

All of these basic event handlers are added via the Page class or its derivatives. We have already seen an example above of adding a handler via XML but the handlers can also be added via Java with just as much ease. Here's an example that is equivalent to the above XML:

Adding an event handler with Java

  1. addMouseHandler( btnProceed, "nextPage" );
  2. addMouseHandler( btnCancel, "cancel" );

Accessing event objects

In the case of multiple event being processed by a single event handler it is sometimes useful to query the properties of the event that caused the handler to be invoked. This is frequently the case with mouse events and key events (and special helper methods are defined for working with mouse events).

To access the event that triggered the handler it is necessary to call the getCurrentEvent method. The event can then be cast to whatever the appropriate type and the event specific data is then accessible. The example below shows how information is extracted from a mouse event. Note that the event is first checked to ensure that it is a mouse event before attempting to cast the value.

Accessing the current event

  1. public void handleMouseClicks()
  2. {
  3. if ( wasMouseClicked()) {
  4. MouseEvent me = (MouseEvent)getCurrentEvent();
  5. Point pt = me.getPoint();
  6. statusLabel.setText( "The mouse is at: + Integer.toString(pt.x) + "," +
  7. Integer.toString(pt.y));
  8. }
  9. }

Under the hood

In adding event handlers Aria has to go through a number of steps and it is worth understanding these steps, if only for the sake of in-depth knowledge.

Consider first of all the process of adding the event handler. Aria constructs your page and the user interface for the page. This is really a process of adding the user interface components to an instance of your page's class. That class is derived from Page which includes the event handling mechanism. In processing the event declaration in your page's XML Aria therefore uses the page's event handling mechanism.

Like most normal Java code Aria pages respond to event through the standard listener interfaces and to save coding the page implements a number of the more common listener interfaces. The page can therefore listen for the common events associated with these interfaces. One of the most common is the ActionListener interface which is invoked in response to an event such as a button click. So when you ask a Aria page to add an actionHandler for a button it justs adds itself as an ActionListener for the button.

Once the button is clicked the page is notified through the interface. The method you registered is then looked-up and invoked via reflection (since Aria doesn't really have any knowledge of your method). No great magic there, but the process saves some more of the plumbing code.

Custom event handlers

It is possible to add support for just about any event type to a Aria application. The EventAdapter interface is an interface that wraps an object implementing the particular listener of interest. The wrapper is intended to be generic and therefore delegates most of its work to the Aria event handler.

Adding a custom event handler

  1. <Event method="tabChanged"
  2. target="tabPanel"
  3. type="org.formaria.events.swing.ChangeEventHelper"/>

The ChangeEventHelper implements the ChangeListener interface and in responding to ChangeEvents it invokes the named method. The framework assumes that the class referenced is an instance of the EventAdapter interface, which specifies the parameters needed to create and add the event handler.

The complete source for the above helper is shown below

ChangeEventHelper, a custom event handler

  1. package org.formaria.events.swing;
  2.  
  3. import javax.swing.event.ChangeEvent;
  4. import javax.swing.event.ChangeListener;
  5. import org.formaria.aria.events.EventAdapter;
  6. import org.formaria.aria.events.AriaEventHandler;
  7.  
  8. /**
  9.   * A helper for the Swing ChangeListener interface
  10.   * <p>Copyright (c) Formaria Ltd., 2009
  11.   * <p>License: see license.txt
  12.   * $Revision: 1.2 $
  13.   */
  14. public class ChangeEventHelper implements ChangeListener, XEventAdapter
  15. {
  16. protected AriaEventHandler eventHandler;
  17.  
  18. /**
  19.   * Set the current event handler
  20.   * @param xeh the event handler
  21.   */
  22. public void setEventHandler( AriaEventHandler xeh )
  23. {
  24. eventHandler = xeh;
  25. }
  26.  
  27. /**
  28.   * Get the name of the adder method e.g. addActionListener
  29.   * @return the method name
  30.   */
  31. public String getAddedMethodName()
  32. {
  33. return "addChangeListener";
  34. }
  35.  
  36. /**
  37.   * Get the name of the listener e.g. java.awt.event.ActionListener
  38.   * @return the listener name
  39.   */
  40. public String getListenerInterfaceName()
  41. {
  42. return "javax.swing.event.ChangeListener";
  43. }
  44.  
  45. /**
  46.   * Get the event mask
  47.   * @return the mask e.g. AWTEvent.ACTION_MASK
  48.   */
  49. public long getEventMask()
  50. {
  51. return 0x100000;
  52. }
  53.  
  54. /**
  55.   * The change event has occured
  56.   */
  57. public void stateChanged( ChangeEvent e )
  58. {
  59. try {
  60. eventHandler.invoke( getEventMask(), e );
  61. }
  62. catch ( Exception ex ) {
  63. ex.printStackTrace();
  64. }
  65. }
  66. }
  67.  

In the case of the above class the listener specific code is the stateChanged method, but all that does is delegate to the event handler.

Exception handling

Any application logic relying on user input is susceptible to a variety of errors and exceptional conditions that make process impossible to complete. In the Java world it is normal to trap these exceptions as they occur.

In Aria exception handling is closely tied to the event handling mechanism and the validation mechanism. The idea is that exceptions and validation errors can be handled in a manner similar to the event handling mechanism. Furthermore exceptions can be handled with a certain degree of separation from the normal flow of events since, as the name suggests they occur in exceptional circumstances.

For example an incomplete form could potentially produce a series of exceptions during event processing and during validation. From a user point of view it is unlikely that individual warnings would be produced for each error and instead the user would be informed of a set of errors or a synopsis of the errors. Aria supports this type of error handling and also redirection of errors to a central error handler. You can find more information on exception handling and input validation at See Writing a custom exception handler.

Generic event handling

Aria has built in handlers for the event types listed above however in some cases it is not adequate for all demands or it may be necessary to handle some other event types. Therefore the Aria API includes a number of methods to assist in integration of other event types.

To customize the event handling it is necessary to subclass the Page class. In subclassing Page there are three possible mechanisms for changing the event handling.

First the setEventHandler method can be used to set a specific event handler. Changing the event handler gives considerable scope to change the behavior of the event handling within Aria.

Secondly individual events can be added with the methods listed below. Choosing to add or modify specific events means that the changes will have less impact than modifying the event handler but at the cost of more work if the changes are to be reused elsewhere.

Finally the underlying Java event handling mechanisms can be used to handle events just as would be the case in a plain old Java application. Nothing in Aria should interfere with the basic event handling mechanism.

Event Handler API

  1. public void setEventHandler( EventHandler eh );
  2.  
  3. public void addListener( Object comp, String listenerName, String argType );
  4. public void addHandler( Object comp, long eventType, String methodName ) throws Exception;

The first method setEventHandler sets the event handler class for the page. This method should be called in the page constructor. The page delegates to the event handler whenever an attempt is made to add a new event handler.

Most applications call specific methods in the page's API such asaddActionHandler, which in turn delegates to the event handler instance. The event handler is essentially a helper class that takes care of adding the event listener (by calling addListener and addHandler) and mapping the listener to a response method in the page. Further information on these APIs can be found in the API documentation.

Under the hood the Page class implements the methods specified by the ActionListener interface. In adding an event the page adds itself as a listener to the target component. When the event of interest is fired the page therefore responds to the event via this listener interface. The page then searches for the handler (that you had registered) and invokes it.

Extended Event Specification

As we saw in the earlier data binding chapter the syntax for dynamic method bindings has been extended to include referencing of classes other than the class upon which the current page is based.

The obvious difference from the data binding syntax is that an event binding sets up a reference to a method (the event handler method) and does not itself cause the method to be evaluated. The syntax for the extended event bindings is as follows:

Event specifications

Syntax

Role

mypackage.MyClass.myMethod

to invoke a static method

mypackage.MyClass[].myMethod

to create a new instance of the class on each evaluation

mypackage.MyClass[referenceName].myMethod

for a named object instance

myMethod[referenceName]

for a method contained with the invoking page

[referenceName].myMethod

for a method contained with the class instance referred to by the reference name.

To illustrate this extended event specification syntax let's have a look at an example.

Counter example extended with library functions

  1. <Page class="com.mypackage.ui.swing.SimpleCalculator">
  2. <Components>
  3. <Edit name="Counter" ... />
  4. <Panel ...>
  5. <Button name="DecBtn" ... content="${org.formaria.library.Calculator.getDecLabel()}"/>
  6. <Button name="ClearBtn" content="${org.formaria.library.Calculator[foo].getClearLabel()}"/>
  7. <Button name="IncBtn" ... content="${[foo].getIncLabel()}"/>
  8. <Button name="divideBtn" ... content="/"/>
  9. <Button name="multiplyBtn" ... content="X"/>
  10. </Panel>
  11. </Components>
  12. <Events>
  13. <Event method="doEquals" target="ClearBtn" type="ActionHandler"/>
  14. <Event method="org.formaria.library.Calculator.increment"
  15. target="IncBtn" type="ActionHandler"/>
  16. <Event method="org.formaria.library.Calculator[].decrement"
  17. target="DecBtn" type="ActionHandler"/>
  18. <Event method="org.formaria.library.Calculator[foo].divide"
  19. target="divideBtn" type="ActionHandler"/>
  20. <Event method="[foo].multiply" target="multiplyBtn" type="ActionHandler"/>
  21. </Events>
  22. </Page>

In the above example we are recreating the simple calculator that we have used in other examples. While the example is a contrived (we will document some more realistic examples later) it shows the new syntax in action.

Line 14 sets up an event binding to a static member of the org.formaria.library. Calculator class

Line 15 constructs a new instance of the class each time the event is bound (when the XML expression is loaded and evaluated).

Line 16 constructs a new instance and sets up a reference to the new instance. If the instance already existed it would be reused. In this case a instance of 'foo' had already been created at line 7 so that instance is reused instead of creating a new instance.

Lines 17 and 18 create bindings to other methods in the class and as in the case of line 16 they each use the 'foo' object created on line 7.

The importance of this simple extension should not be under estimated. Quite simply this extension means that your event handlers do not necessarily have to exist in a class derived from Page. In fact the extension means that you can build libraries of utility functions for common tasks and these functions can be used for things like navigation, calculations and other tasks that do not have to be tied to a particular page.

It is also worth noting that this extension mechanism does not mean that the library functions built using the mechanism cannot access the current page or the current component. Such values can be passed as arguments to the function are they can be accessed via the project, the page, the current event and the component hierarchy.

Adding event types

The EventHandler class loads a file events.xml to get a specification of the events it is to handle. The event handler can be instructed to handle other event types by adding additional configuration files. The easiest way to do this on a per project basis is to add an events.xml file to the project's resources. A startup parameter EventHandlers controls the name of the file loaded, but if none is found Aria attempts to load events.xml.

The default events.xml file is as follows:

Default event specification

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <events>
  3. <event name="ActionHandler" interface="java.awt.event.ActionListener" mask="128"/>
  4. <!-- mask="AWTEvent.ACTION_EVENT_MASK"/-->
  5. <event name="MenuHandler" interface="java.awt.event.ActionListener" mask="128"/>
  6. <!-- mask="AWTEvent.ACTION_EVENT_MASK"/-->
  7. <event name="FocusHandler" interface="java.awt.event.FocusListener" mask="4"/>
  8. <!-- mask="AWTEvent.FOCUS_EVENT_MASK"/-->
  9. <event name="TextHandler" interface="java.awt.event.TextListener" mask="1024"/>
  10. <!-- mask="AWTEvent.TEXT_EVENT_MASK"/-->
  11. <event name="ItemHandler" interface="java.awt.event.ItemListener" mask="512"/>
  12. <!-- mask="AWTEvent.ITEM_EVENT_MASK"/-->
  13. <event name="KeyHandler" interface="java.awt.event.KeyListener" mask="8"/>
  14. <!-- mask="AWTEvent.KEY_EVENT_MASK"/-->
  15. <event name="MouseHandler" interface="java.awt.event.MouseListener" mask="16"/>
  16. <!-- mask="AWTEvent.MOUSE_EVENT_MASK"/-->
  17. <event name="MouseMotionHandler" interface="java.awt.event.MouseMotionListener" mask="32"/>
  18. <!-- mask="AWTEvent.MOUSE_MOTION_EVENT_MASK"/-->
  19. </events>

Navigation and Page Management

Aria provides all the infrastructure necessary to manage pages within an application whether the pages are created with XML or via Java. The page manager at the heart of Aria also provides support for framesets and page history management.

The page management function is separated from the application and from the individual pages to provide independence of the underlying widget set.

Navigation

In Aria each page can be regarded as a discrete entity displayed by the application or applet. All pages must be derived from the basic Page class and this class interacts with the PageManager class to provide several methods of changing the displayed page. The page manager acts to maintain references to pages so that they do not need to be recreated whenever they are displayed, hidden or redisplayed.

In order to get a reference to the PageManager for the current project the call below can be used.

Retrieving the PageManager for the current project

  1. ProjectManager.getPageManager();

The main methods are showPage and showPrevious , which unsurprisingly show a new page and redisplay the previously selected page.

To invoke these methods it is necessary to add a way of initiating the navigation action. Normally this is accomplished by having a button or hostpot on the page with a text or graphic to indicate that a click will cause, say the next page to be displayed.

In the example below the user can initiate the navigation action by clicking on a button.

Setting up the navigation button and response event

  1. <Page class="myPackage.MyCustomPageClass">
  2. <Components>
  3. ...
  4. <Button name="btnProceed" x="40" y="10" w="60" h="20" content="Proceed"/>
  5. </Components>
  6. <Events>
  7. ...
  8. <Event method="proceed" target="btnProceed" type="ActionHandler"/>
  9. </Events>

The page must then implement the proceed response method. The Page class contains a reference to the PageManager for the current Project and can be used directly from subclassed Pages as shown below:

Implementing the response method

  1. package myPackage;
  2.  
  3. import org.formaria.aria.*;
  4. import org.formaria.swing.*;
  5. import org.formaria.aria.data.*;
  6. import org.formaria.aria.helper.*;
  7.  
  8. public class MyCustomPageClass extends Page
  9. {
  10.  
  11. public MyCustomPageClass()
  12. {
  13. }
  14.  
  15. ...
  16.  
  17. public void proceed()
  18. {
  19. pageMgr.showPage( "Page2");
  20. }
  21. }
  22.  

The page manager

The Aria page manager provides a mechanism for loading and displaying pages. It takes care of managing the page's lifecycle so that you need not be concerned with when and how a page is loaded or destroyed.

When an application requests a page by calling the showPage method the actual loading of the page is delegated to the page manager. The page manager will then attempt to load an XML file corresponding by name to the requested page. If an XML file is not found then the page manager will attempt to load a Java class of that name (and failing that, a dummy page can be created).

The page manager also takes care of the page lifecycle, calling the pageCreated, pageActivated and pageDeactivated methods as appropriate.

As the pages are separated from one another and from the applet class through this page manager mechanism the pages can be loaded independently of each other (unless explicit links or references are inserted). Furthermore, as the page manager is also separate from the applet and the widget set it is possible to code a large variety of utility functions so that they can work with different widget sets such as Swing and AWT.

Showing a page

A few simple methods are provided to allow loading and display of pages. It is generally assumed that all page handling will be delegated to the page manager and this is the case whenever the Page 's methods are used to display pages and this is the case for the normal response methods used in Aria applications.

The showPage method simply takes the name of a page. An alternative form of the showPage method can also take a second parameter indicating the target area (but more on this later). The showPage method loads the page as described above and requests that the applet displays the page. Implicitly in this process any currently visible page is deactivated and hidden.

Once a page has been shown it is regarded as the current page and at any point the current page can be referenced by calling the page manager's getCurrentPage method.

The showPage call can also include a URL so that the page can be loaded from a remote source. In fact most resource specifications in Aria can include URLs in this way.

Targets

Aria supports the notion of framesets similar to those used in HTML. Like HTML the framesets in Aria are defined in a separate frames file (pointed to by the startup properties file).

Each of the areas referenced by the frames file is known as a target area. Most of the page management methods include versions that take a target area name as a parameter and these functions affect the named area. Thus if you use the showPage method with a target area name, the area named in the method call is updated instead of the application's entire content area.

In cases where a frameset is used it is still possible to call the single argument versions of the page management functions. It is then assumed that the target area name defaults to ` content'.

Generally this default name works well where the frameset is being used to provide navigation facilities and other long lived facilities such as banners and footer. (see also Framesets.)

Page history

In addition to the showPage method Aria also provides the showPrevious method. As a page is shown, the history/order of the displayed pages is saved and the showPrevious method allows you to reverse through this order of pages. The showPrevious method works in much the same way as showPage and in fact relies on the same mechanisms to update the application.

Page history should be used with caution as it does not necessarily correspond to the end user's notion of what the previous page was. Furthermore if the user has navigated through a loop of pages then over use of the showPrevious method could lead to confusion.

Frequently there may be a need for multiple navigation mechanisms to help avoid such situations. It may be that it is just the user's perception of the order of pages that is out of sync with the way things are implemented and therefore we recommend adding visual feedback to your application to help support navigation.

Visual feedback should let the user know where they are within the application, how far they have progressed and how far they have to go to completion. The type of feedback used will of course vary from application to application and will also depend on the visual styles employed but whatever the detail, such hints greatly aid usability.

Resetting the page cache

On rare occasions it may be necessary to reset the page cache and page history. Some instances of this occur whenever the bound data is changed or reloaded from a different source, when a language changes or when it is necessary to reset all pages, removing user inputs.

To reset the page cache in this way use the following code:

Reset the page cache

  1. // Use the Page's member variable pageMgr!
  2. pageMgr.reset();

Page loaders

Aria employs an extensible method of loading pages. The page manager normally loads pages from XML or from Java classes if it cannot find a suitable XML page. This process can be altered by setting up secondary page loaders. The page loader is described by the PageLoader interface and can be configured through the startup property value for BuilderClass and NumBuilderClasses. Aria includes several page loaders including Aria's default page loader, an HTML page loader (see Importing HTML.) and a generic page loader for loading forms and other file formats (see Importing forms.).

Dataset defined navigation

When applications which use pages in a particular sequence are being written such as application forms consisting of multiple pages or wizard applications it is possible to define those pages in a static XML dataset file. For example in the code sample below, pages are defined within in a dataset file for a wizard type application.

  1. <Datasets>
  2. <dataset id="navigation">
  3. <dataset id="wizardpages">
  4. <data value="welcome"/>
  5. <data value="projectinit" desc="WIZ_PROJ_SET"/>
  6. <data value="languagepage" desc="WIZ_PROJ_LANG"/>
  7. <data value="colourscheme" desc="WIZ_PROJ_LAYOUT"/>
  8. <data value="finish" desc="WIZ_PROJ_FINISH"/>
  9. </dataset>
  10. </dataset>
  11. </Datasets>
  12.  

This dataset file needs to be referenced from the datasources file which is in turn loaded from the ModelData startup property. For more on loading datasets see See Data management.. Once loaded, the application will have access to these definitions via the DataModel for the project. There are three possible paths declared in this wizard dataset, `wizardpages', `generationpages' and `buildpages'. The relevant path can be set upon clicking a button, a menu or seleting a radio button.

A singleton class can then be written which takes care of the navigation and of course the pages referenced by the value attribute of the model nodes need to exist on the classpath.

NavigationManager Singleton class

  1. public class NavigationManager {
  2.  
  3. BaseModel navMdl;
  4. protected int currentNode = 0;
  5. BasePage currentPage;
  6. private static NavigationManager navMgr;
  7.  
  8. private NavigationManager( String navPath ) {
  9. navMdl = ( BaseModel ) ProjectManager.getModel().get( "navigation/wizardpages" );
  10. }
  11.  
  12. public static NavigationManager getInstance()
  13. {
  14. if ( navMgr == null )
  15. navMgr = new NavigationManager()
  16.  
  17. return navMgr;
  18. }
  19.  
  20. public static void next()
  21. {
  22. getInstance.showNextPage();
  23. }
  24.  
  25. public static void prev()
  26. {
  27. getInstance.showPrevPage();
  28. }
  29.  
  30. public void showPrevPage()
  31. {
  32. currentNode--;
  33. showpage();
  34.  
  35. }
  36.  
  37. public void showNextPage()
  38. {
  39. if ( currentPage == null ) {
  40. BaseModel mdl = (BaseModel) navMdl.get( currentNode );
  41. String pagename = (String) mdl.get();
  42. currentPage = (BasePage) ProjectManager.getPageManager().getPage( pagename );
  43. }
  44. if ( currentPage.checkValidations() == 0 ) {
  45. currentNode++;
  46. showpage();
  47. }
  48. }
  49.  
  50. public void showHomePage()
  51. {
  52. currentPage = null;
  53. currentNode = 0;
  54. BaseModel mdl = (BaseModel) navMdl.get( currentNode );
  55. String pagename = (String) mdl.get();
  56. currentPage = (BasePage) ProjectManager.getPageManager().showPage( pagename );
  57. }
  58.  
  59. private void showpage()
  60. {
  61. BaseModel mdl = ( BaseModel ) navMdl.get( currentNode );
  62. String pagename = ( String ) mdl.get();
  63. currentPage = ( BasePage ) ProjectManager.getPageManager().showPage( pagename );
  64. }
  65.  
  66. }

This mechanism can be used in conjunction with Library Functions to manage the page navigation in a simple and easy to maintain way. Take the following XML page declaration for example.

A navigation page XML declaration

  1. <Page class="net.co.test.NavPanel" style="Heading">
  2. <Components>
  3. <Image name="nextButton" x="564" y="51" w="19" h="18" content="right.gif"/>
  4. <Image name="prevButton" x="60" y="54" w="19" h="18" content="left.gif"/>
  5. </Components>
  6. <Events>
  7. <Event method="net.co.test.NavigationManager.next" target="nextButton" type="MouseHandler"/>
  8. <Event method="net.co.test.NavigationManager.prev" target="prevButton" type="MouseHandler"/>
  9. </Events>
  10. </Page>
  11.  

This code will call the static methods of the NavigationManager class and the NavigationManager will take care of presenting the correct page. Of course this can be extended to take care of multiple routes through an application or to repeat certain pages which will bind to different parts of the model and it should be obvious that pages can be added or rearranged without any difficulty.

Mapping page names

A page name can now be paramaterized or mapped, so that a frames file or startup.properties file can specify a key instead of a page name. This key can then be mapped to a specific page depending on the context or on startup parameters.

Setup the application lifecycle listener to set the initial page name

  1. // In startup.properties specifc the Lifecycle object
  2. // LifeCycleListener=my.project.ProjectListener
  3.  
  4. // In that object map the "START_PAGE" page name
  5. public class ProjectListener implements LifeCycleListener
  6. {
  7. /**
  8.   * Called when the application/applet has been created and initialized.
  9.   * @param project the owner project
  10.   */
  11. public void initialize( Project p )
  12. {
  13. String[] args = (String[])project.getObject( "StartupArgs" );
  14. // Set the start page to the 3rd command-line parameter
  15. p.getPageManager().mapPageName( "START_PAGE", args[ 3 ] );
  16. }
  17. }

and instead of hardcoding a page name in the frames file it can be replaced with the appropriate key:

Set the content using a key instead of a page name

  1. // In the frames.xml include the "START_PAGE" key
  2. <Frame name="content" constraint="content" width="500" height="200"
  3. content="START_PAGE" title="Home" icon="ac0036-16.png" sidebar="west"/>

Application Styles

Aria supports multiple application styles, including Single Document Interface (SDI) and Multiple Document Interface (MDI) styles with and without docking of the internal windows.

Using the various application types you can create a variety of applications from the simple to the complex. Most applications will rely on an SDI style of interface for simplicity, but others such as management consoles and tool style applications can make good use of a docking framework to help manage the user interface complexities that arise from hierarchies and structures of the application domain.

Application styles are simple dictated by the startup applet used to launch an application and most of the remaining code and configuration is common to all applications types. In some cases, switching between application styles is also possible, provided that attention is paid to things like page navigation and framesets.

Application styles

Aria supports multiple application styles as metntioned above. These styles are:

Application styles

Style

Description

Basic

With the basic application style you get a single window in which single pages or simple framesets can be displayed. This type of setup corresponds toa Single Document Interface

Desktop

The desktop application style is equivalent to the Multiple Document Interface or MDI, where multiple windows can be used and each has a header, allowing the window to be moved around a desktop panel.

Docking

The docking application style is almost a cross between SDI and MDI applications, whereby multiple windows are allowed, but where those windows are managed either by arranging them in tabbed windows or by docking them into sidebars.

Generally it seems that users prefer SDI type applications , or some form of docking, however some application such as complex management applications may justify use of MDI. The choice of one application style depends on your particular application and changing from one to another is just a matter of choosing the appropriate startup class.

Docking Frames

Docking frames allow multiple windows, with headers, titles and icons to be displayed in various areas of the screen. Multiple windows or targets can be placed in each of these areas. The docked windows can be minimized to sidebars and later restored to their original location. The docked windows may also be dragged from one area to another thereby allowing users to customize the layout of their application.

Docking frames also show a preview of the docked window when the mouse is moved over the sidebar button. Using these `sliding' windows can save valuable screen real estate and help reduce clutter. A user might, for instance, minimize a window to the sidebar when the information contained in that window is only of occassional interest. Then, in order to consult the window all she need do is move the mouse over the sidebar button to view the window contents once more. A simple click will then dismiss the preview.

A docked panel may also be zoomed in on so that it occupies almost all the available screen area (apart from the toolbars, menus and sidebars). To zoom in on a panel just double click the header, then to restore the view just double click the header once more.

Using the docking layout

As mentioned above, to use the docking layout you just need to select the appropriate startup application org.formaria.swing.docking.DockingApp

Startup for the docking application style

  1. java -cp AriaRuntimeCommon.jar org.formaria.swing.docking.DockingApp

The FrameTest sample application can be seen below when used with the Docking application style. The application is shown with half of its windows minimized, two to the south or bottom docking sidebar and one to the east or right handside sidebar

Setting the layout configuration

The basic frameset is specified using the frames file that was used for the basic frames setup earlier (see Framesets.), but for docking layouts we must add some extra information detailing how the screen is to be divided up. For example the following:

Setting up a docking layout

  1. <FrameSet layout="(COLUMN (ROW weight=1.0 left
  2. (COLUMN middleTop content middleBottom) right) bottom)">
  3. <Frame name="left" constraint="left" content="p1"
  4. icon="aria_icon.png" sidebar="west" title="Dock to the left"/>
  5. <Frame name="right" constraint="right" content="p2" icon="aria_icon.png" sidebar="east"/>
  6. <Frame name="middleTop" constraint="middleTop" content="p3" icon="aria_icon.png" sidebar="west"/>
  7. <Frame name="middleBottom" constraint="middleBottom" content="p4" icon="aria_icon.png" sidebar="south"/>
  8. <Frame name="content" constraint="content" content="Welcome" icon="aria_icon.png" sidebar="west"/>
  9. <Frame name="bottom" constraint="bottom" content="p6" icon="aria_icon.png" sidebar="south"/>
  10. </FrameSet>

The docking layout is specified as an attribute of the FrameSet element, and follows the format specified by the SwingLabs MultiSplitLayout layout manager (see http://today.java.net/pub/a/today/2006/03/23/multi-split-pane.html ).

Docking options

Each of the docked frames has a number of properties that yo can use to customize the appearance of the docked frame, and these are:

Docking frame attributes

Attribute

Usage

icon

Specifies the name of an icon to use in the frame's header.

sidebar

The name of the side bar into which this target docks when minimzed. The options are:

west - dock into the west or left hand side sidebar

south - dock into the south or bottom edge sidebar

east - dock into the east or right hand side sidebar

title

The title of the frame.

constraint

The name of the docking area into which this frame or panel normally docks. The constraint should match one of the areas specified by the FramsSet 's config attribute.

minimized

If true , sets the initial state of the panel to be minimized.

Embedded applications

Not all applications are standalone and in some cases an application needs to coexist with legacy functionality or functionality obtained from a third party. Therefore there are situations where an application does not have direct control of its startup or of its frame. For this purpose Aria and Aria applications can be embedded in a standard Swing application as though the Aria application was just a Panel or just another component being used by the application.

The key to embedding a Aria application is the XContentHolder interface which describes the content displayed to the user. The interface is used by the page manager to display the pages that makeup the Aria application.

Modular application

Just as a Aria application can be embedded in a legacy application, one Aria application can be embedded in another. In fact multiple Aria applications can coexist within the one JVM. Applications used in this way can also exist as standalone entities in their own right. Turning this about, applications can therefore be built in a modular way, such that a component or module can also exist and operate as a complete application.

Building applications in a modular way allows you to deliver functionality in a targeted way, depending on things like user roles/needs, security/access rights, connectivity levels, development schedules or even for commercial reasons (e.g. advanced features for power users). With a modular architecture you can be more targeted with your delivery of functionality.

Aria's support for modular applications extends throughout the platform and apart from the loading of the modules you can largely regard the separate modules as being part of the complete application from a development standpoint.

Set startup objects

Multple startup objects can be added simply by adding startup property entries in the form:

Listing the startup objects

  1. StartupObject<N>=Name;className
  2. StartupObject<N>=Name;className;Project

where is a counter starting from 0. In the second example the object is constructed via a constructor that takes an instance of the project as an argument

While this may seem like an insignificant change it opens up the possiblity of having multiple applications running within the same JVM and it means that an application can be built from modular elements, elements that can exist as standalone applications or as integrated modules.

Relaunching applications

Support for the system tray or launch area has been added. When a system tray icon is added the application can choose to stay resident in memory after the main window has been closed by setting the " ExitOnClose " startup property to false. The advantage of doing this is that the data model does not need to be reinitialized if the main window is just being redisplayed. This initialization can be significantly quicker than normal startup as all the classes needed by the JVM are already loaded. If remote data is used the startup gain may be even greater as network access may not be required.

The tray icon provides two menu items, one to open the window and the second to close the window and exit the JVM.

This version of the class uses the SwingLabs version of the tray support and is therefore limited to Swing. If JDK 6 or later is being used then the JVM's tray support could be used for non Swing applications.

The system tray/stay resident option may be particularly valuable when used in conjunction with an embedded webserver, as a remote server could thereby signal the application to wake up and display new data or alerts as when such data becomes available by simply making a request to the client application's server. The same process could also be used to provide a tighter integration between a web page/web application and a Aria application.

To use this support the application must add an instance of the org.formaria.swing.deploy.SystemTrayManager class. A good place to do this is in a LifeCycleListener implementation

Setting up the system tray

  1. public class ProjectListener implements LifeCycleListener
  2. {
  3. private static SystemTrayManager sysTray;
  4. /**
  5.   * Called when the application/applet has been created and initialized.
  6.   * @param project the owner project
  7.   */
  8. public void initialize( Project proj )
  9. {
  10. final Project project = proj;
  11. if ( sysTray == null ) {
  12. sysTray = SystemTrayManager.getInstance( project );
  13.  
  14. // Do remaining initialization and network access
  15. }
  16. }
  17.  
  18. /**
  19.   * Called when the application/applet has been shutdown and is about to exit
  20.   */
  21. public void shutdown()
  22. {
  23. }
  24. }

Embedded webserver

A minimal http server is now embedded at org.formaria.http.HttpServer . When customized this server will allow a server appplication to signal a client via a http request. By customizing the server with a response handler the application can then respond with special actions, such as a wake-up or by requesting updates/new data from the server.

JNLP support

As an alternative to using the embedded webserver an application can use the JNLP API to request that it is a singleton, that is that only one instance of the application should run at a time. The SystemTrayManager takes care of most of the work and it will invoked the Activation interface specified by a project if one is present if an attempt is made to relaunch the JWS application while the instance is still in memory . The activation object is specified as follows sometime following startup:

Setting the activation object

  1. project.setObject( "ActivationObject", this );

Typically the application will restart its user interface once reactivated. Of course care should be taken to do this on the EventDispatchThread.

A typical restart handler2

  1. public void activate()
  2. {
  3. String[] args = (String[])project.getObject( "StartupArgs" );
  4. final String eventType = args.length > ProjectListener.EVENT_TYPE_ARG ?
  5. args[ ProjectListener.EVENT_TYPE_ARG ] : null;
  6. final String eventID = args.length > ProjectListener.EVENT_TYPE_ID ?
  7. args[ ProjectListener.EVENT_TYPE_ID ] : null;
  8.  
  9. // Query the startup event in the background
  10. if (( eventID != null ) &amp;&amp; ( eventID.length() > 0 )) {
  11. SwingWorker worker = new SwingWorker()
  12. {
  13. public Object construct()
  14. {
  15. // Do the work off the EDT
  16. String url = "workspaces/myevent/" + eventID;
  17.  
  18. ProjectListener.queryEvents( project, eventType, url );
  19. return null;
  20. }
  21.  
  22. public void finished()
  23. {
  24. // Now, on the EDT update the tree and show the event.
  25. getBinding( taskList ).get();
  26. showEvent( eventID, eventType );
  27. setSelectedNode( eventID );
  28. }
  29. };
  30. worker.start();
  31. }
  32. }
  33.  

To use this type of functionality the application must make sure that the application does not exit the JVM when the main frame is closed, and this is done with by overloading the XLifeCycleListener's shutdown method - the tray manager takes care of the rest

Prevent JVM Exit

  1. /**
  2.   * Initialize the project's connection
  3.   */
  4. public class ProjectListener implements LifeCycleListener
  5. {
  6. ...
  7.  
  8. /**
  9.   * Called when the application/applet has been shutdown and is about to exit
  10.   */
  11. public void shutdown()
  12. {
  13. }

Input Validation

Aria input validation allows you to define your validations in a single file so that the validation rules contained in the file can be used application wide or even across applications. Built-in validations provide common validations such as `mandatory' and `min-max' validations. The validation rules provide predictable and consistent behavior making your code much easier to maintain and extend than the alternative of checking individual inputs on an ad hoc basis.

The validation mechanism can also make it easier to localize an application or customize things like input limits for different environments or contexts.

In this section you will see how to implement the simple validations and how to handle validation errors and exceptions. The chapter will also show how to build customized validations.

Adding Basic Validations

The first step in adding input validations is to define our validation rules in an XML file and amend the startup.properties file to refer to the file.

Create a new file and name it ` validations.xml '. In the file create a simple validation file in the resources directory of your project.

A simple validation file

  1. <Validations>
  2. <validation name="firstname" type="mandatory" msg="Firstname cannot be blank"/>
  3. </Validations>

The name attribute is what we refer to when referencing the validation from a page. The type attribute defines the validation type and the msg attribute is the message which is shown should the validation fail.

The startup.properties file needs to refer to the new validations file. Setting the validations file can be carried out through the project properties page within the editor or manually by adding the following line to the startup.properties file. If no `Validations' property is specified the application will default to `validations.xml' and use it if found.

New line in the startup.properties

  1. Validations=validations.xml

Now a page can be created with an edit field which needs to be validated. To provide data for the field and to map the input field to the data model a binding can be added within the data section. A button has also been added which will be used to trigger the validation.

The validated page

  1. <Page class="org.formaria.test.Personal" style="prompt">
  2. <Components>
  3. <Label x="144" y="110" w="100" h="20" style="prompt" content="Firstname:"
  4. alignment="Left" opaque="false"/>
  5. <Edit name="firstnameText" x="294" y="108" w="178" h="20" style="data" alignment="Leading"/>
  6. <Button name="validateBtn" x="220" y="250" w="200" h="30" content="Validate"/>
  7. </Components>
  8. <Events>
  9. <Event method="doValidation" target="validateBtn" type="ActionHandler"/>
  10. </Events>
  11. <Validations>
  12. <Validation rule="firstname" target="firstnameText"/>
  13. </Validations>
  14. <Data>
  15. <Bind target="firstnameText" source="customer/firstname" output="customer/firstname"/>
  16. </Data>
  17. </Page>

Once the validation rule has been setup and bound to a component, the validation needs to be triggered and validation error messages also need to be presented to the user.

From the page's XML file it can be seen that the class which implements the event handlers is ` org.formaria.test.Personal '. For now, simply insert the following function into the class:

Prepare to handle validations with an event handler function

  1. public void doValidation()
  2. {
  3. int ret = checkValidations();
  4. }

If the application is run and the button clicked, the Java console will show the exceptions being thrown with the text of the validation. If some text is entered into the textbox and the button clicked again no exception is now thrown indicating that everything is OK.

The checkValidations function returns a value indicating the status. A decision can be made what to do depending on the value returned from the function. The return values are defined as constants in the org.formaria.aria.validation.Validator interface.

This code works but it is not a particularly elegant way of handling the validations and the section Writing a custom exception handler., will also look at how to handle and display the validations in a more programmer friendly way.

Adding component validations

The validation described above was triggered by a programmed event and the same validation would also be triggered upon a page transition. Component validations on the other hand are triggered when the user interface component being validated has lost focus. Start by opening the validations.xml file and adding a new minmax validation. The new validation appears as below

An in-line validation

  1. <validation name="age" type="minmax" min="18" max="65"
  2. msg="Customer age must be between {min} and {max}" mandatory="false"/>

In this case the type is ` minmax ' indicating that this validation is to be used with numeric data. The ` min ' and ` max ' attributes contain the limits of the input. The message contains the two pieces of text ` {min} ' and ` {max} '. These values will be substituted with the values contained in the ` min ' and ` max ' attributes whenever the validation is triggered. The ` mandatory' attribute indicates whether or not the field must contain data when the page level validations are being carried out.

Next add another edit component to the ` personal ' page and add the validation below.

  1. <Validation rule="age" target="ageText"/>

Start the application once more and enter a value outside the specified range into the new edit field. Hit the tab key and you will notice an exception being output in the Java console. Do the same with a value within the range and no exception is output.

To highlight the behavior of the validation a few input combinations are listed. For clarity enter some data into the firstname edit so that it does not trigger the mandatory validation.

  • Clear the data in the age edit field and click the button. No exception is output as the mandatory attribute is set to false .
  • Enter some invalid data into the edit field, hit the tab field to generate the exception and click the button. In this case the page level validation triggers an exception as the text entered into the field is outside the permissible range even though the mandatory flag is false .
  • Change the mandatory flag to true and restart the application. Leave the age field blank and click the button. In this case an exception is triggered as the field cannot be blank.

Validation attribute evaluation

Aria 3.0 extends the use of evaluated attributes to most areas of the framework, including validations. Evaluated attributes allow you to change the behavior of a validation at runtime by way of callbacks to your code or to library code.

Handling exceptions

The attribute evaluator contains an exception handler that gets called in case of an exception and can override the result returned by the attribute evaluator. The evaluator may be of use in case, for example, an evaluation depends on a list selection and where that list may not have a selected values - the list would otherwise return a value such as null or -1 to indicate the error and this is probably not a valid value for the evaluated attribute. Say a path of a/b/${c}/d/e is enetered and ${c} depends on say a list selection and that list is not fully initialized.

Validation lifecycles

Validation can be invoked at various times within an application. For the most part the lifecylce of an individual validation instance is tied to the lifecycle of the component and page to which it is bound.

At the finest level a validation may be triggered by a change in a components data, or by intervention by the programmer, while at a courser level the validations can be triggered prior to page transition.

Validation on Page Transition

Normally validations are checked when a page transition occurs if the TriggerValidations startup property is set, however on versions of Aria prior to 3.2 this value was not added to the startup file by the New Aria Project wizard , so it wass necessary to set the value manually.

Once the TriggerValidations flag is set a validation error will prevent the page transition from occuring.

Writing a custom exception handler

The method of reporting the validations up to now is crude and does not give you much control of error reporting. In order to manage the validations a custom exception handler can be written.

Writing a custom exception handler is straightforward and allows you the same code to be used for error handling on all of pages in an application. A new class named ` ExceptionHandler ` which provides an implementation of the ExceptionHandler interface is created.

The ExceptionHandler interface contains two methods.

The handleException method definition

  1. public boolean handleException( Object c, Exception ex, Object checker );

The handleException method takes three parameters and returns a boolean value. The first parameter is the component being validated, the second the exception, and the third is the validator which is carrying out the validation. The method should return true if further validations are to be carried out and false if no more validations are required. This chaining of validations allows you to control how the validations are used and how error messages are displayed. Stopping a chain of validations allows you to prevent the user being bombarded with error messages if, for instance, a single input field triggers multiple validation errors.

The accumulateMessages method definition

  1. public int accumulateMessages( boolean accumulate, int level );

Another method is now required, the accumulateMessages method is called twice when the Page.checkValidation s method is invoked. The first time is before any validations have been done so as to inform the ExceptionHandler that page level validations have begun. In this case the first parameter is true . Then, it is called once more when all of the validations have completed and in this case the first parameter is false .

The second parameter indicates the most serious level of error encountered so far. the value of this parameter can be overridden within the implementation of the accumulateMessages method by returning a different value from the method. Otherwise it is expected that the second parameter is returned.

Now we will implement our custom ExceptionHandler class as shown below. The class is shown in its entirety as it is to explain it section by section when the context of each section can be seen.

The entire ExceptionHandler class

  1. package org.formaria.test;
  2.  
  3. import java.util.*;
  4. import java.awt.*;
  5. import org.formaria.aria.*;
  6. import org.formaria.aria.exception.*;
  7. import org.formaria.aria.validation.*;
  8.  
  9. public class ExceptionHandler implements ExceptionHandler
  10. {
  11. boolean pageValidation = false;
  12. private Page currentPage;
  13. Vector errors, warnings;
  14.  
  15. public ExceptionHandler( Page page )
  16. {
  17. currentPage = page;
  18. }
  19.  
  20. public boolean handleException( Object comp, Exception ex, Object xvalidator )
  21. {
  22. Validator validator = ( Validator )validator;
  23. if ( ( validator.getLevel() == validator.LEVEL_ERROR ) && ( ! pageValidation ) ){
  24. currentPage.showMessage( "Input error", ex.getMessage() );
  25. return true;
  26. }
  27. String msg = validator.getMessage();
  28. if ( validator.getLevel() == validator.LEVEL_ERROR )
  29. errors.add( msg );
  30. else
  31. warnings.add( msg );
  32.  
  33. return true;
  34. }
  35.  
  36. public int accumulateMessages( boolean start, int level )
  37. {
  38. pageValidation = start;
  39. if ( pageValidation ){
  40. errors = new Vector();
  41. warnings = new Vector();
  42. } else {
  43. if ( errors.size() > 0 || warnings.size() > 0 ) {
  44. Toolkit.getDefaultToolkit().beep();
  45. currentPage.showMessage( "Error", formatMessages() );
  46. }
  47. }
  48. return level;
  49. }
  50.  
  51. private String formatMessages()
  52. {
  53. String msg = "";
  54. for ( int i = 0; i < errors.size(); i++ )
  55. msg += "error :" + errors.elementAt( i ) + "\n";
  56. for ( int i = 0; i < warnings.size(); i++ )
  57. msg += "warning :" + warnings.elementAt( i ) + "\n";
  58. return msg;
  59. }
  60. }

The constructor takes the page being validated as a parameter. This is stored in a member variable for use when the validations are being done.

The ExceptionHandler constructor

  1. public ExceptionHandler( Page page )
  2. {
  3. currentPage = page;
  4. }

The handleException method handles component and page level validations. In the case of component validations the level will be XValidator.LEVEL_ERROR passed exception is simply displayed.

In the case where the pageValidation flag is true the message is added to the appropriate storage. The idea behind this is that the reporting of the errors can be controlled instead of having to display individual messages (and require the user to dismiss multiple error messages) or only most severe errors need be displayed.

Implementing the handleException method

  1. public boolean handleException( Object comp, Exception ex, Object xvalidator )
  2. {
  3. Validator validator = ( Validator )validator;
  4. if ( ( validator.getLevel() == validator.LEVEL_ERROR ) && ( ! pageValidation ) ){
  5. currentPage.showMessage( "Input error", ex.getMessage() );
  6. return true;
  7. }
  8. String msg = validator.getMessage();
  9. if ( validator.getLevel() == validator.LEVEL_ERROR )
  10. errors.add( msg );
  11. else
  12. warnings.add( msg );
  13.  
  14. return true;
  15. }

The accumulateMessages method is shown below

Implementing the accumulateMessages method

  1. public int accumulateMessages( boolean start, int level )
  2. {
  3. pageValidation = start;
  4. if ( pageValidation ){
  5. errors = new Vector();
  6. warnings = new Vector();
  7. } else {
  8. if ( errors.size() > 0 || warnings.size() > 0 ) {
  9. Toolkit.getDefaultToolkit().beep();
  10. currentPage.showMessage( "Error", formatMessages() );
  11. }
  12. }
  13. return level;
  14. }

The first line sets the member variable ` pageValidation ' to the value passed in the ` start ' parameter.

If start is true the storage vectors are initialized; one to store warnings and the other to store errors.

Alternatively, if start is false something needs to be done with the errors and warnings that have been accumulated. In this case, the showMessage method of the currentPage is called which will display the errors and warnings in a simple text dialog with an OK button. A decision on what to do next can be made in the ` personal ' page where the checkValidations method was called depending on the values returned from the accumulateMessages method.

As mentioned above this error handling mechanism provides control over the error reporting mechanism and another way to handle the validations in the case where there are only warnings is to display a dialog listing the warnings and asking `Do you wish to continue?' along with ` Yes ' and ` No ' buttons. If the Yes button is clicked then the value XValidator.LEVEL_IGNORE can be returned which will give the page the impression that no validations were generated.

Writing a custom validation factory and custom validations

The basic ValidationFactory class can construct the validation types discussed so far but custom validations might be required, and this is done by means of writing a custom validation factory.

Start by creating the class AValidationFactory which extends the ValidationFactory class.

The custom AValidationFactory

  1. package org.formaria.test;
  2.  
  3. import java.awt.*;
  4.  
  5. import org.formaria.debug.*;
  6. import org.formaria.xml.*;
  7. import org.formaria.aria.build.*;
  8. import org.formaria.aria.validation.*;
  9.  
  10. public class AValidationFactory extends ValidationFactory {
  11.  
  12. public Validator getValidation( String validationName, Method m, int mask, Object page )
  13. {
  14. return super.getValidation( validationName, mask, page );
  15. }
  16.  
  17. }

For now the new class simply implements the getValidation method and returns the call to the super method.

In order to start using this custom class the following line needs to be added to the startup.properties file

The startup.properties line

  1. ValidationFactory=org.formaria.test.ValidationFactory

If the application is run now, it will work exactly as before except that our new validation factory is now controlling the construction of the validation rules.

Now a custom validation class can be created. As an example a simple number validation class can be created which will make sure that entered data is numeric. Create a class called NumberValidation which extends the BaseValidator class. Create the validate method which carries out the validation. In the NumberValidator class the Double.parseDouble method is used to parse the text contained in the component. If an exception occurs it can be assumed that the validation has failed, the errorLevel is set and an exception is thrown.

The NumberValidation class

  1. package org.formaria.test;
  2.  
  3. import java.awt.*;
  4.  
  5. import org.formaria.aria.validation.*;
  6.  
  7. public class NumberValidation extends BaseValidator {
  8.  
  9. public void validate(Object c, boolean forceMandatory) throws Exception
  10. {
  11. String text = getText(c);
  12. if (text.trim().length() > 0) {
  13. try {
  14. Double.parseDouble(text);
  15. }
  16. catch (Exception ex) {
  17. errorLevel = LEVEL_ERROR;
  18. throwException();
  19. }
  20. }
  21. }

Now the custom validation factory class needs to be amended to create and return the NumberValidator class whenever the text ` number ' is passed in the validationName parameter of the getValidation function.

The modified getValidation method

  1. public Validator getValidation( String validationName, Method m, int mask, Object page )
  2. {
  3. XmlElement ele = ( XmlElement ) validations.get( validationName );
  4. String type = ele.getAttribute( "type" );
  5.  
  6. if ( type.compareTo( "number" ) == 0 ) {
  7. NumberValidation validator = new NumberValidation();
  8. validator.setName( validationName );
  9. validator.setMask( mask );
  10. validator.setup( ele );
  11. return validator;
  12. } else {
  13. return super.getValidation( validationName, mask, page );
  14. }
  15. }

The line ` validator.setup(ele) ' deserves some explanation as this allows whatever arbitrary attributes might be required for the custom validator to be set. The ele variable is the entire validation node as defined in the validations.xml file and can be interrogated by the custom validator for whatever tags are used.

Now open the validations file and enter a new validation of type ` number '

Declaring a new number validation

  1. <validation name="price" type="number" msg="The price must be a number"/>

Next, open the personal.xml file and enter a definition for a new edit component and name it priceText . Now enter a new validation for the component as follows

  1. <Validation rule="price" target="priceText"/>

If the application is run now it can be seen that the new field has page level as well as component validations. You can remove the component validation by changing the validate function as follows.

  1. public void validate(Object c, boolean forceMandatory) throws Exception {
  2. if (forceMandatory) {
  3. String text = getText(c);
  4. if (text.trim().length() > 0) {
  5. try {
  6. Double.parseDouble(text);
  7. }
  8. catch (Exception ex) {
  9. errorLevel = LEVEL_ERROR;
  10. throwException();
  11. }
  12. }
  13. }
  14. }

The forceMandatory flag is set to true when the validation is page level so by doing nothing when it is false removes the component validation.

Finally the new validation class is changed to handle the mandatory attribute. This is achieved by overloading the setup method of the BaseValidator class as mentioned earlier.

The overloaded setup class

  1. public void setup( XmlElement ruleConfig, XmlElement instanceConfig )
  2. {
  3. String value = ruleConfig.getAttribute( "mandatory" );
  4. mandatory = value.compareTo( "true" ) == 0 ? true : false;