You are hereAria User Guide / Section II: Introduction / Building Pages with Java
Building Pages with Java
Building pages with Java
Building a Aria application with Java is a little more complicated than using XML by its very nature however if the same sort of steps are followed in constructing the code then a simple UI application will result.
Java has many advantages over XML and notable amongst these is the reusability engendered by the object oriented nature of the language. Pages created in XML can be reused and extended (they can even be extended with XML). While an XML page is processed once as the page is loaded a Java page can have an extended lifecycle and hence a more extensive setup process. However, that said, Java and XML can be used in unison, mixing and matching as appropriate for your application.
Many of the steps and techniques used in the last chapter will be reused in this chapter, so even if you are not using XML for page declarations we would advise your to read the preceeding chapter for background information. The steps involved are much the same and differ only in implementation. Since the native Java objects are being used directly instead of via XML more features will be available, but for the purposes of the simple examples being used few of these additional features are required. As with other introductory chapters later chapters will cover the features employed in more detail.
Basic form building
At its very simplest a Aria-Java page requires a class derived from Page , the basis of all Aria pages. The traditional HelloWorld example can thus be coded:
Hello World coded in Java
import org.formaria.aria.*; import org.formaria.swing.*; public class HelloWorld extends Page { public HelloWorld() { componentFactory.addComponent( LABEL, 10, 40, 90, 20, "Hello World" ); } }
In the above example the page's base class, Page , has defined a component factory to help construct components. The addComponent method of this factory is used to create a new component at the specified position, add it to the page, and sets its content. The constant ' LABEL ' is also defined by Page as a convenience to help identify the component type consistently. Constants are defined for all the built in types, other types are identified by name (a string value).
The use of the factory saves several method calls that are normally used when an IDE generates the UI. The more compact form produced makes it easier to see what is going on and in the long run it is thus easier to maintain.
Using stylesheets
Stylesheets specify sets of colors and fonts. These styles are arranged hierarchically and are intended to add consistency to an application's look. Using styles with the component factory also saves some more setup code.
Like many of the components in Aria the styles are defined in a styles file. The startup file should include a setting for a ' StyleFile ' which points to the styles file.
Startup Properties including a style file reference
UseWindow=false ClientWidth=240 ClientHeight=300 StartPackage=org.formaria.samples.simple StartClass=StyleSheet Title=Simple Form Sample StyleFile=stylesheetstyle.xml
To use the styles the component factory addComponent method signature has been extended to take an extra parameter, the name of the style. The Label components might all use the explicit style 'prompt', whereas the Edit components are styled implicitly by including the Edit style (if it exists) in the stylesheet. In fact each component has a default style name based upon its type name, but you can override these defaults and specify the style explicitly by appending the style name to the addComponent method call. The fragment below shows two such component additions.
Adding a label and an edit field
componentFactory.addComponent( LABEL, 5, 10, 90, 20, "Firstname:", "prompt" ); txtFirstname = (XEdit)componentFactory.addComponent( EDIT, 100, 10, 100, 20, "Joe" );
The construction of the buttons shows the explicit and implicit use of styles. Following on from this the ' Proceed ' button has the XButton style applied because it does not specify a style whereas the ' Cancel ' has specified it's own style.
Adding OK and Cancel buttons
btnOK = (Button)componentFactory.addComponent( BUTTON, 40, 10, 60, 20, "Proceed" ); btnCancel = (Button)componentFactory.addComponent( BUTTON, 110,10,60, 20, "Cancel", "BtnReverse" )
The result of this is shown below. What you see should be identical to what you would have seen had the page been constructed via XML:
Event handling
Using the same page as in the previous section we can see how events are hooked up easily to components without the need to add listeners or import the usual event packages.
Events are added by calling the addSomeEvtHandler of the Page, where SomeEvt is the event type, for example Item , Mouse , Action and so on. These calls take two parameters, the first being the component which we want to attach the event to and the second being the method to call when the event is triggered.
Adding event handlers
private void addEvents() { addTextHandler( txtCarCost, "changeAdvance" ); addTextHandler( txtDeposit, "changeAdvance" ); addMouseHandler( btnOK, "proceed" ); }
The called methods must be declared as public void .
Simple event handlers
public void changeAdvance() { float cost = Float.parseFloat( txtCarCost.getText() ); float deposit = Float.parseFloat( txtDeposit.getText() ); txtAdvance.setText( String.valueOf( cost-deposit) ); } public void proceed() { if ( wasMouseClicked() ) btnOK.setLabel( "Clicked!" ); }
The example illustrates a common error in adding events. In the implementation of the proceed method above you can see that we can use the Page.wasMouseClicked() method to determine if it was a `click' which triggered the event. The mouse handler is invoked for multiple events like the mouse press, mouse move and mouse release events and we do not want to respond to all of those events.
The problem is that although we may be responding to a mouse click, the appropriate handler for a button click is in fact an action handler. Therefore instead of using the addMouseHandler the addActionHandler should have been used. Correcting this error the declaration now looks like:
Revised addition of event handlers
private void addEvents() { addTextHandler( txtCarCost, "changeAdvance" ); addTextHandler( txtDeposit, "changeAdvance" ); addActionHandler( btnOK, "proceed" ); }
With this revision we can simplify the response method to remove the unnecessary checks.
Revised simple event handlers
public void changeAdvance() { float cost = Float.parseFloat( txtCarCost.getText() ); float deposit = Float.parseFloat( txtDeposit.getText() ); txtAdvance.setText( String.valueOf( cost-deposit) ); } public void proceed() { btnOK.setLabel( "Clicked!" ); }
If you do indeed need to do more analysis of the event which triggered the call you can access the event with the Page.getCurrentEvent() method and check the event type and properties. The EventHandler returned from the getCurrentEvent() method needs to be cast to the appropriate Handler type depending on how it was declared.
Data binding
Continuing with the example, we next add data bindings to put some content into the page. In the most basic setup, input controls can be bound to data stored in an XML file. Static data is data contained in flat files bundled with the application. Such data is often used for test purposes or during initial development of an application.
To work with data bindings a little configuration is needed and the startup file now requires the entry ModelData=simpledatasets.xml . The referenced file contains the static data. The data in the file is then bound to the input controls using TextBinding objects.
Inserting data bindings
private void addBindings() { addBinding( new TextBinding(txtFirstname, "customer/firstname") ); addBinding( new TextBinding(txtSurname, "customer/surname") ); addBinding( new ListBinding(cmbAge, "customer/ageRanges") ); addBinding( new ListBinding(txtCarCost, "financial/carcost") ); addBinding( new TextBinding(txtDeposit, "financial/deposit") ); }
Validations
Validations are rules that can be used to check that user input is correct. Reusable validation rules are specified in an XML file (named validations.xml by default). For example:
Validation rules
<Validations> <validation name="firstname" type="mandatory" msg="The [firstname] cannot be blank" mandatory="true"/> <validation name="surname" type="mandatory" msg="The [surname] cannot be blank" mandatory="true"/> <validation name="age" type="minmax" min="18" max="100" msg="The [age] ({value} [years]) must be between {min} [years] and {max} [years]" mandatory="true"/> </Validations>
Here three validation rules are specified for the example shown below. The first two rules merely check that some input has been provided while the third rule checks that an age value is within a minimum and maximum range.
Older versions of the Aria platform required an explicit call to setup the validation handling with a call to the ' setExceptionHandler ' method of the Page , this is no longer the case.
To use the validations we need to bind the validation to the components with a call to addValidation .
Binding the validatin rules to input fields
addValidation( txtFirstname, "firstname" ); addValidation( txtSurname, "surname" ); addValidation( txtAge, "age" );
Putting the complete example together we get:
The complete example
package org.formaria.samples.simple; import org.formaria.aria.*; import org.formaria.aria.data.*; public class DataBinding extends Page { Edit txtFirstname, txtSurname, txtCarCost, txtDeposit, txtAdvance; ComboBox cmbAge; Panel pnlClientInfo, pnlFinancialInfo, pnlButtons; Button btnOK, btnCancel; public DataBinding() { pnlClientInfo = (Panel)componentFactory.addComponent( PANEL, 10, 10, 210, 80 ); pnlFinancialInfo = (Panel)componentFactory.addComponent( PANEL, 10, 100, 210, 80 ); pnlButtons = (Panel)componentFactory.addComponent( PANEL, 10, 200, 210, 40 ); pnlClientInfo.setDrawFrame( Panel.BORDER_BEVEL ); pnlFinancialInfo.setDrawFrame( Panel.BORDER_BEVEL ); pnlButtons.setDrawFrame( Panel.BORDER_BEVEL ); componentFactory.setParentComponent( pnlClientInfo ); componentFactory.addComponent( LABEL, 5, 10, 90, 20, "Firstname:", "prompt"); txtFirstname = (Edit)componentFactory.addComponent( EDIT, 100, 10, 100, 20 ); componentFactory.addComponent( LABEL, 5, 30, 90, 20, "Surname:", "prompt"); txtSurname = (Edit)componentFactory.addComponent( EDIT, 100, 30, 100, 20 ); componentFactory.addComponent( LABEL, 5, 50, 90, 20, "Age:", "prompt"); cmbAge = (ComboBox)componentFactory.addComponent( COMBO, 100, 50, 100, 20, null, "Edit" ); componentFactory.setParentComponent( pnlFinancialInfo ); componentFactory.addComponent( LABEL, 5, 10, 90, 20, "Car cost:", "prompt"); txtCarCost = (Edit)componentFactory.addComponent( EDIT, 100, 10, 100, 20 ); componentFactory.addComponent( LABEL, 5, 30, 90, 20, "Deposit:", "prompt"); txtDeposit = (Edit)componentFactory.addComponent( EDIT, 100, 30, 100, 20 ); componentFactory.addComponent( LABEL, 5, 50, 90, 20, "Advance:", "prompt"); txtAdvance = (Edit)componentFactory.addComponent( EDIT, 100, 50, 100, 20 ); txtAdvance.setEnabled( false ); componentFactory.setParentComponent( pnlButtons ); btnOK = (Button)componentFactory.addComponent( BUTTON, 40, 10, 60, 20, "Proceed" ); btnCancel = (XButton)componentFactory.addComponent( BUTTON, 110, 10, 60, 20, "Cancel", "BtnReverse" ); addEvents(); addBindings(); } public void pageActivated() { changeAdvance(); } private void addEvents() { addTextHandler( txtCarCost, "changeAdvance" ); addTextHandler( txtDeposit, "changeAdvance" ); addActionHandler( btnOK, "proceed" ); } private void addBindings() { addBinding( new TextBinding(txtFirstname, "customer/firstname") ); addBinding( new TextBinding(txtSurname, "customer/surname") ); addBinding( new ListBinding(cmbAge, "customer/ageRanges") ); addBinding( new TextBinding(txtCarCost, "financial/carcost") ); addBinding( new TextBinding(txtDeposit, "financial/deposit") ); } public void changeAdvance() { float cost = Float.valueOf( txtCarCost.getText() ).floatValue(); float deposit = Float.valueOf( txtDeposit.getText() ).floatValue(); txtAdvance.setText( String.valueOf( cost - deposit) ); } public void proceed() { btnOK.setLabel( "Clicked!" ); } }
The example makes use of the page's lifecycle method, pageActivated to call the changeAdvance method and update the page once all the components have been setup and the data loaded.
Annotations
If you use Java 5 and later you can take advantage of some annotations to save on coding. The annotations replicate features that can be coded directly in early versions of Java, so you are not missing out on any features if you cannot use Java 5 in this context. The annotations are as follows:
|
Annotation |
Role |
|---|---|
|
@ Find |
The find annotation performs the same function as the findComponent method, except that no typecasting is needed. An example of this is as follows:
@Find XButton myButton;
This annotation finds the reference to an Button instance with the name ` myButton '. |
|
@ Bind( path ) |
Like the find method above this annotation adds a binding to the specified path to the annotated component. |
|
@ Validate( rule ) |
The Validate annotation associates the specified validation rule with the annotated component. |
|
@ Event( method=, type= ) |
The Event annotation adds an event handler to the annotated component of the specified type. The response method is specified by the method argument. |
The annotations are probably of most use to the Java programmer, but the @Find annotation can save a significant amount of boiler plate code for all users, thereby making the code more readable.
The annotations require that you include the appropriate classes from the org.formaria.annotation package. See your IDE's documentation for inserting and optimizing imports. (In NetBeans press ALT+SHIFT+F to find the necessary imports).
Buddy helpers
In some ways Java coding is more complex than the equivalent XML however once we get over that initial hurdle Java coding can be considerably simpler. One common example of this is the BuddyHelper class that is used to setup labeled components.
Using the BuddyHelper, one call can setup two (or even three) components. The helper is designed to add a label (and sometimes a suffix for say a dimension). The helper not only saves a call to the component factory for adding the label but it also makes it a little easier to get alignments correct. (Remember that layout managers are normally preferable to the null layout).
In the above example the name edit fields could be added with this code:
A buddy in use
BuddyHelper buddy = new BuddyHelper( (StyleFactory)componentFactory ); imgSingle = (Edit)buddy.addComponent( EDIT, 5, 100, 10, 200, 20, panelWidth, 28, "First Name", "", "prompt" ); imgDouble = ( Edit )buddy.addComponent( EDIT, 5, 100, 30, 200, 20, panelWidth, 28, "Second Name", "", "prompt" );
Aria has many other helpers like this that can help in building Java application. These helpers are not restricted to UI component creating and they cover all areas of Aria including the data model, events and so on and are available throughout the lifecycle of the application.
Other helpers
Aria provides numerous helper classes aimed to simplify Java coding and to remove those verbose typecasting requirements.
|
NavigationHelper |
NavigationHelper extends Page by adding a few simple methods to assist in navigating from page to page. |
|
DebugLogger |
Keep track of errors and warnings and give some statistics when the application shuts down as a debugging aid. |
|
MessageHelper |
Formats messages (for 1.1.x JDKs) |
|
NumberFormatter |
A utility to help format numbers in accordance with the current Locale |
|
DoubleAdapter |
A helper for an double model field |
|
IntegerAdapter |
A helper for an integer model field |
|
TableModelHelper |
A utility class to help construct a HTML like table structure |
|
ModelNodeHelper |
A helper to eliminate some typecasting with the DataModel |
Logging
For most applications it is useful to use logging during the develop stage of a project.
Aria provides the DebugLogger class to provide integrated logging features. This log class keeps track of the number of errors and warnings generated. The amount of logging output can also be configured by setting a log level in this class. Ultimately almost all logging can be suppressed by setting the log level to SILENT . This log level is application wide and can be set or reset at any point in the application lifecycle.
At startup the ' LogLevel ' property is used to set the initial value.
Aria itself uses logging extensively and this can lead to verbose console output. To further help cut down on the logging Aria can be built in two forms - with debug logging on or off. The BuildProperties.DEBUG flag controls this logging.
The flag is defined statically so that the compiler will eliminate any code that is excluded by the flag (when it is set to false). This same mechanism can be used to conditionally include logging code in your pages:
Adding debug logging
if ( BuildProperties.DEBUG ) </P> DebugLogger.logWarning( "Some log message" );</P>
The actual mechanism used for logging can be controlled by the LogWriter startup parameter, but normally this is configured for you and should need no change.
Compilation and building
Java applications need to be compiled before they can be run. Aria provides facilities to do this building but it is also possible to build an application from the command line or from a third party tool.
Compiling an application is fairly straightforward as all the classes needed for a build are contained in the AriaRuntimeCommon.jar file. Individual files can be compiled with the Javac command on the command-line, but we recommend using the ANT build tool.
However, as the extent and complexity of a project increases it will be worth considering using the tools that Aria provides by way of NetBeans or tools such as Eclipse or their commercial equivalents such as JBuilder. These tools provide much more than just compilation support and can be of great benefit when testing and debugging a project.
Switching toolkits
When building an Aria application with Java it is necessary to derive the page from the Page class and add components to that page. As part of this process it is necessary to import the definitions of the various objects used in the page, including the Aria components. At this point it is therefore necessary to choose between the AWT and Swing toolkits as one or the other must be imported.
This import implies a dependency on one of the toolkits. However given judicious programming it should not be a big task to move from one toolkit to the other if you have only used Aria components and have avoided toolkit specific features.
In fact all you need do to convert from one toolkit to the other is to change the imports, recompile and test. Of course there will be some differences in the look and feel of the application but by in large the behavior should not change.
Switching toolkits is probably a rare occurrence yet it still remains a good idea to try and reduce dependencies with an application. If possible you should try and isolate dependence of one toolkit or another if only for the purpose of keeping your code clean and clear.
Beyond the basics
Sometimes what is built into a platform may not cover all your needs and it is necessary to add extensions and third party components. Aria is based on an open design and with Java at the core there are many ways to add and extend the platform.
For example if components outside of the basic framework are employed then they can be registered with Aria, See Choosing how to install components. and accessed with the techniques described above. Since third party components are not built-in Aria can only have a limited view or understanding of those components and therefore you may need to manipulate the components directly. Programming in Java allows you do this and once you obtain a reference to a component you can access all its features either directly through the public API or through reflection.