The Mortgage application which is outlined as part of this case study is taken from the tutorial which is available for download from the Aria project on the sourceforge site. The intention of the tutorial is the make the developer familiar with development elements of the Aria/Aria framework. The development of the application is split over several different tutorials which gives the developer a fully working version of the application at the end of each step.
In this case study we are going to examine the elements which make up the application in each tutorial. In summary the tutorials are as follows
This step shows how to set up the basic structure of a Aria application and what files and libraries need to be included. It creates some simple pages and shows how to navigate the application. It shows how to add page events, validations, data bindings and dynamic bindings. The application is completely localized in the final step and the model is used to output captured data.
This step builds upon the introductory tutorial and provides the user with a more generic navigation which allows new pages to be added very easily. It also shows how library functions can be used to help with the navigation. Custom validations are created which are loaded via a custom ValidationFactory. Framesets are customised and component registration is used to develop and load custom components. Custom dialogs are created which report on the types of errors being generated. Model data is saved from and restored to the model showing how easy it is to create applications where projects can be created and opened.
This tutorial really replicates what was done in the first step except that the development is carried out with the help of the Aria Editor editor.
In introducing Aria the application is makes use of the routes which are available in the Aria library. This allows the developer to create applications which can easily connect to a servlet server making use of authentication and session management services. Data can be cached on the client and the application can be used to work with them offline and then synchronise with the server. Custom ServiceProxy classes are created which are used in transforming mode data.
This tutorial begins by showing the project structure for a typical Aria application as shown in the diagram below.
![]() |
The run.bat file contains the text below.
The run.bat file
java -cp .;lib;AriaRuntimeCommon.jar;images;pages; resources;build;classes org.formaria.swing.Applet
The application is made up of frames which are defined in the frames.xml file as shown below.
The frames.xml file
<FrameSet> <Frame name="banner" constraint="NORTH" content="banner" width="640" height="96"/> <Frame name="content" constraint="CENTER" content="Welcome" width="640" height="288"/> <Frame name="navPanel" constraint="SOUTH" content="navPanel" width="640" height="96"/> </FrameSet>
The project navigation was handled by the navPanel page in the navPanel frame. In order to do this two images where added to the navPanel page as shown below
navPanel.xml page
<Page class="org.formaria.mortgage.NavPanel" style="Heading"> <Components> <Image name="nextButton" x="564" y="51" w="19" h="18" content="right.gif"/> <Image name="prevButton" x="60" y="54" w="19" h="18" content="left.gif"/> </Components> <Events> <Event method="nextPage" target="nextButton" type="MouseHandler"/> <Event method="prevPage" target="prevButton" type="MouseHandler"/> </Events> </Page>
Two images are added and each has a MouseHandler event added to them. The nextPage and prevPage functions are defined in the NavPanel class as follows.
The NavPanel class
package org.formaria.mortgage; import org.formaria.aria.*; public class NavPanel extends Page { public void nextPage() { if ( wasMouseClicked()) navigateToPage( "next" ); } protected void navigateToPage( String key ) { Page target = ( Page )( ( Target ) pageMgr.getTarget( "content" ) ).getComponent( 0 ); String dest = ( String )target.getAttribute( key, null ); if ( dest != null ) pageMgr.showPage( dest, "content" ); } public void prevPage() { if ( wasMouseClicked() ) pageMgr.showPrevious(); } }
This navigation depended on the presence of the next attribute in the main content page
Dynamic binding was used to address different nodes within the data model. On the personal page the binding are declared as follows.
The bindings on the personal page
... <Data> <Bind target="firstnameText" source="mortapp/${getCustomerID()}/firstname" output="mortapp/${getCustomerID()}/firstname" /> <Bind target="surnameText" source="mortapp/${getCustomerID()}/surname" output="mortapp/${getCustomerID()}/surname" /> <Bind target="dobText" source="${getCustomerID()}/dob" output="mortapp/${getCustomerID()}/dob" /> <Bind target="titleList" source="defaults/titleList" output="mortapp/${getCustomerID()}/title" /> </Data> ...
These bindings depend on the callback function getCustomerID in the Personal class as follows.
The callback function in the Personal page
public String getCustomerID() { return "customer" + ( String )currentCust.get(); }
This class takes care of tracking which customer is being currently processed allowing numerous customers to be added to the model in their own specific nodes.
The model data was then saved within the Finish class pageActivated function.
The Finish class
public class Finish extends Page { public void pageActivated() { try { String path = System.getProperty( "user.dir" ) + File.separator + "save.xml"; FileOutputStream fos = new FileOutputStream( path ); OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF8" ); BufferedWriter bw = new BufferedWriter( osw ); DataSource.outputModel( bw, ( ( DataModel ) rootModel.get( "aria_state/mortapp" ) ) ); bw.flush(); bw.close(); } catch (Exception e) { System.out.println( "error" ); } } }
This provided us with the following output.
Model data saved from the application
<data id="mortapp"> <data id="customer1"> <data value="Joe" id="firstname" /> <data value="Bloggs" id="surname" /> <data value="14/12/1975" id="dob" /> <data value="Mr" id="title" /> </data> <data id="customer2"> <data value="Josephine" id="firstname" /> <data value="Bloggs" id="surname" /> <data value="01/04/1972" id="dob" /> <data value="Mrs" id="title" /> </data> <data id="finance"> <data value="250000" id="propvalue" /> <data value="200000" id="mortamt" /> </data> </data>
The entire application was localized through the use of language specific resource bundles. For example the personal page was localized simply by changing the content attributes for the components.
The localized personal page
<Page next="personal" class="org.formaria.mortgage.Welcome" resource=""> <Components> <Label x="10" y="5" w="80" h="20" style="prompt" content="WEL_LANG" alignment="Left" opaque="true"/> <Combo name="languageList" x="90" y="5" w="120" h="20"/> <Label x="130" y="84" w="350" h="60" style="Heading" content="WEL_TEXT" alignment="Left" opaque="true"/> <Panel x="0" y="150" w="650" h="50" style="banner/prompt"> <RadioButton name="soleRadio" x="226" y="15" w="100" h="20" style="banner/prompt" content="WEL_SOLE" alignment="Leading"/> <RadioButton name="jointRadio" x="330" y="15" w="100" h="20" style="banner/prompt" content="WEL_JOINT" alignment="Leading"/> </Panel> </Components> ...
The resources were entered into two resource bundles for English and French.
Extract from en.properties
WEL_LANG=Language WEL_TEXT=Welcome to ABC Bank's mortgage application. Before we proceed please note the following... WEL_SOLE=Sole WEL_JOINT=Joint BAN_TITLE=ABC Bank Mortgage Application BAN_CLOSE=Close Application PER_HEADING_1=Personal Details (First Applicant) PER_HEADING_2=Personal Details (Second Applicant) ...
And the French translation:
Extract from fr.properties
WEL_LANG=Langue WEL_TEXT=Bienvenue ? la demande de pr?t hypoth?caire de l'hypoth?que de la banque de ABC. Avant que nous veuillez proc?der note le suivant... WEL_SOLE=Unique WEL_JOINT=Joint BAN_TITLE=Demande de pr?t hypoth?caire D'Hypoth?que De Banque de ABC BAN_CLOSE=Application ?troite PER_HEADING_1=D?tails Personnels (Premier Demandeur) PER_HEADING_2=D?tails Personnels (Deuxi?me Demandeur) ...
A combo box was then placed on the welcome page which allowed the user to switch language. The selection of a language invoked the following code in the Welcome class.
Language switching code
public void changeLanguage() { if ( langClicked ) { String lang = ( String )languageList.getSelectedObject(); pageMgr.reset(); String langCode = lang.compareTo( "English" ) == 0 ? "en" : "fr"; project.setStartupParam( "Language", langCode ); pageMgr.loadFrames( "frames", true ); } }
This completed the introductory tutorial and the resulting application can be seen below
![]() |
This tutorial uses the application created in the introductory tutorial and begins by adding custom validations and a custom validation factory. The custom validation calls a function within the Page and returns the result of it. The custom validation is shown below.
The custom FunctionValidation factory
public class FunctionValidation extends BaseValidator { public FunctionValidation() { } public void validate( Object c, boolean forceMandatory ) throws Exception { Integer ret = ( Integer ) invokeMethod(); if ( ret.intValue() > LEVEL_IGNORE ) { errorLevel = ret.intValue(); throwException(); } } public void setup( XmlElement element ) { message = element.getAttribute( "msg" ); super.setup( element ); } }
In order to use this validation a custom validation factory need to be created as shown below.
The custom ValidationFactory class
public class ValidationFactory extends ValidationFactory { ... public Validator getValidation( String validationName, Method m, int mask, Object page ) { XmlElement ele = ( XmlElement ) validations.get( validationName ); String type = ele.getAttribute( "type" ); if ( type.compareTo( "function" ) == 0 ) { FunctionValidation validator = new FunctionValidation(); validator.setName( validationName ); validator.setValidationMethod( m, page ); validator.setup( ele ); return validator; } else { BaseValidator validator = (BaseValidator)super.getValidation( validationName, mask, page ); validator.setup( ele ); return validator; } } }
The getValidation function checks to see if the type being created is function . If it is it creates a new instance of the FunctionValidation class and returns it. If it isn't the super getValidation function is called in order to retrieve the validation.
In order for Aria to use this validation factory the following line needed to be added to the startup properties.
Startup property for the custom validation factory
ValidationFactory=org.formaria.mortgage.validation.ValidationFactory
Now the welcome page was amended to use the new validation.
XML declaration for the function validation
<Validation rule="createApp" target="jointRadio" method="checkCreateSetup"/>
And the welcome class was amended to include the checkCreateSetup function
The checkCreateSetup function
... public Integer checkCreateSetup() { int level = BaseValidator.LEVEL_IGNORE; if ( !jointRadio.isSelected() && !soleRadio.isSelected() ) level = BaseValidator.LEVEL_ERROR; return new Integer( level ); } ...
This function checks to see that at least one of the radio buttons on the welcome page has been selected. If everything is OK then LEVEL_IGNORE is returned otherwise LEVEL_ERROR is returned and the error message is displayed.
The tutorial now goes on to show how the data model can be reopened once saved. This required the use of a custom data binding which was added to a list component on the welcome page.
Binding to the list component on the welcome page
... <List name="fileList" x="0" y="0" w="200" h="100" style="data"/> ... <Bind target="fileList" source="applications" output="temp/filename"/> ...
Now the saved files needed to be listed when the pageCreated function was called.
Loading files into the model
public void pageCreated() { ... loadFiles(); } ... private void loadFiles() { String dir = System.getProperty( "user.dir" ); appsMdl = ( BaseModel )rootModel.get( "applications" ); File folder = new File( dir + ";;files" ); File files[] = folder.listFiles(); for ( int i = 0; i < files.length; i++ ) { String name = files[ i ].getName(); BaseModel filMdl = new BaseModel ( appsMdl, name, name ); } updateBindings(); } ...
Child nodes containing the filenames are added to the applications model node and the updateBindings function of the page is called so that the list will refresh. Now the selected file could be opened and restored into the model as follows.
Opening and restoring the model state
... public void openFile() { String filename = ( String )fileList.getSelectedObject(); // Not shown here but simply reads the file into a string. String contents = getFileContents( filename ); restoreState( contents ); updateBindings(); } private void restoreState( String contents ) { ( ( BaseModel )rootModel.get( "aria_state/mortapp" ) ).clear(); ( ( BaseModel )rootModel.get( "mortapp" ) ).clear(); StringReader sr = new StringReader( contents ); XmlElement ele = XmlSource.read( sr ); if ( ele != null ) { OptionalDataSource ds = new OptionalDataSource(); ds.loadTable( ele, rootModel ); ds.loadTable( ele, ( ( BaseModel )rootModel.get( "aria_state" ) ) ); } } ...
The restoreState function is really of most importance here. It initialises the aria_state and mortapp nodes. It then loads the model into both of these. Again, updateBindings is called so as to refresh the state of the radio buttons on the welcome page.
The list can be seen on the welcome page below.
![]() |
The navigation of the applicaton was improved by making use of the library functions which are part of Aria. To do this the navPanel page was changed to reference functions in the Navigator class as follows.
The amended MouseHandler events in the navPanel page
<Events> <Event method="org.formaria.mortgage.navigate.Navigator.next" target="nextButton" type="MouseHandler"/> <Event method="org.formaria.mortgage.navigate.Navigator.previous" target="prevButton" type="MouseHandler"/> </Events>
The Navigator page was created as a Singleton instance class and included the static functions next and previous . The pages to be included in the navigation were defined in a new navigation dataset as follows.
The navigation.xml file
<dataset id="navigation"> <page id="welcome" value="welcome" /> <page id="personal" value="personal" startrepeat="true" title="PER_HEADING"/> <page id="bank" value="bank" endrepeat="true" title="BANK_HEADING"/> <page id="finance" value="finance" title="FINANCE_TITLE"/> <page id="finish" value="finish" /> </dataset>
The Navigator class took care of keeping track of where the application was in the navigation sequence and of repeating the relevant pages where for the amount of customers being processed. Now each of the pages in the navigation could use the same class to return the correct customer id.
The generic CustomerPage class
public class CustomerPage extends Page { public String getCustomerID() { return "customer" + Navigator.getInstance().getCurrentCust(); } }
With this mechanism the navigation was greatly simplified and pages could be added very easily by referencing them in the navigation file. The amount of customers which could be process is now endless and all taken care of by the very simple Navigation class.
![]() |
The framesets in were amended to include a status frame to show the user exactly where they were in the application navigation. A new frame declaration was added to the frames.xml file as follows.
The new declaration in frames.xml
<FrameSet> <Frame name="banner" constraint="NORTH" content="banner" width="640" height="96"/> <Frame name="appstatus" constraint="WEST" content="appstatus" width="150" height="500" /> <Frame name="content" constraint="CENTER" content="welcome" width="490" height="288" /> <Frame name="navPanel" constraint="SOUTH" content="navPanel" width="640" height="96"/> </FrameSet>
This frame was made invisible on the welcome page and visible on subsequent pages. It's visibilty was controlled by the Navigation class.
A custom component was then created which listed all of the pages to be processed and highlighted the current one. It was declared in the components.xml file as follows.
The components.xml file with the declaration for NavViewer
<Components> <Component name="NavViewer" class="org.formaria.mortgage.comp.NavViewer"> <Property name="Style" mode="Normal" type="both"/> <Property name="DefaultStyle" mode="Normal" type="both"/> <Property name="CurrentStyle" mode="Normal" type="both"/> <Property name="DoneStyle" mode="Normal" type="both"/> <Property name="NavModelPath" mode="Normal" type="both"/> </Component> </Components>
Now a NavViewer instance was added to the navPanel page as follows.
The AppStatus.xml file
<Page class="org.formaria.mortgage.AppStatus" style="Heading" resource=""> <Components> <NavViewer name="navViewer" x="0" y="0" w="145" h="400" Style="status" DefaultStyle="status/default" CurrentStyle="status/current" DoneStyle="status/done" NavModelPath="navigation"/> <Label x="149" y="0" w="1" h="500" style="banner" opaque="true"/> </Components> </Page>
Now the NavViewer class could take care of displaying navigation information as below.
![]() |
A custom dialog was introduced to display errors and warnings with different icons. This new dialog also allowed the user to proceed if only warnings were generated. The exception handler needed to be amended to store the errors and warnings in their own respective vector objects as below.
The ExceptionHandler class
public boolean handleException( Object comp, Exception ex, Object xvalidator ) { ... if ( validator.getLevel() == validator.LEVEL_ERROR ) errors.add( currentPage.translate( msg ) ); else warnings.add( currentPage.translate( msg ) ); return true; } public int accumulateMessages( boolean start, int level ) { pageValidation = start; if ( pageValidation ){ errors = new Vector(); warnings = new Vector(); } else { if ( errors.size() > 0 || warnings.size() > 0 ) return getDialogResult( level ); } return level; } private int getDialogResult( int level ) { ErrorMessage dlg = (ErrorMessage)ProjectManager.getPageManager().loadPage( "ErrorDlg", false ); dlg.setErrorMessages( errors, warnings ); int result = dlg.showDialog( currentPage ); if ( level == Validator.LEVEL_WARNING ) return result == Validator.LEVEL_IGNORE ? Validator.LEVEL_IGNORE : level; else return Validator.LEVEL_ERROR; }
The getDialogResult function creates the new ErrorMessage dialog and passes in the two vectors. The return value determines whether the page can proceed or not depending on what button the user clicked.
In the ErrorMessage class the relevant return value is set when one of the buttons is clicked as follows.
The ErrorMessage class
... public int showDialog( Container owner ) { super.showDialog( owner ); return ret; } public void cancelClicked() { ret = BaseValidator.LEVEL_WARNING; super.closeDlg(); } public void okClicked() { ret = BaseValidator.LEVEL_IGNORE; super.closeDlg(); } ...
This wizard application which will be examined in this section is for generating product selection applications for SMEs. It is a fully functional application and is in use in production environments by some of Formaria's clients. It's intended audience is small to medium companies who wish to manage their product listings and to make those products available within a simple and easy to use application. The typical user would not be particularly technical and with that in mind spreadsheets were chosen as the preferred format for storing product information. The wizard is only the first step in creating the application for the end user and can be used to setup and manage any number of projects.
This application makes use of many of the features of Aria including page navigation, framesets, component registration, localization, validations, the synth look and feel and data binding amongst others.
![]() |
The application consists of a Aria frameset which contains four different target areas. The title frame and navigation frame both remain in the same position with the same content throughout the lifetime of the application. A status frame on the left is initially hidden and is then shown when the user decides on a wizard route. The main content frame takes up the full width of the application when it is first started and contains the WelcomePage Page. Once the status frame appears the width of the main content page is the width of the application less the width of the status frame. The frameset file is defined below.
frames.xml
<FrameSet> <Frame name="banner" constraint="NORTH" content="banner" width="640" height="70"/> <Frame name="wizardstatus" constraint="WEST" content="wizardstatus" width="150" height="500"/> <Frame name="content" constraint="CENTER" content="welcome" width="490" height="324"/> <Frame name="navPanel" constraint="SOUTH" content="navPanel" width="640" height="70"/> </FrameSet>
From the welcome page, it is possible to select one of four radio buttons which will determine the route through the wizard.
The four radio buttons and their declared events
... <RadioButton name="createRad" x="150" y="100" w="250" h="20" style="welcome/prompt" content="WEL_CREATE_PROJ"/> <RadioButton name="generateRad" x="150" y="120" w="250" h="20" style="welcome/prompt" content="WEL_GEN_PROJ"/> <RadioButton name="buildRad" x="150" y="140" w="250" h="20" style="welcome/prompt" content="WEL_BUILD_PROJ"/> <RadioButton name="hotspotRad" x="150" y="160" w="250" h="20" style="welcome/prompt" content="WEL_EDIT_HOTSPOT"/> <Label name="infoLbl" x="100" y="190" w="300" h="80" style="Infotext" alignment="Center" opaque="false"/> ... <Event target="createRad" type="ItemHandler" method="showInfo"/> <Event target="generateRad" type="ItemHandler" method="showInfo"/> <Event target="buildRad" type="ItemHandler" method="showInfo"/> <Event target="hotspotRad" type="ItemHandler" method="showInfo"/> ...
The XML shows the four declarations for each of the radio buttons. The application is completely localized so the content attribute is set to the key for the current resource bundle. The four event declarations are all setup to call the same method showInfo which is shown below. There is also a declaration for a label which will show information for each of the selections and, again, this component uses the current language resource bundle to populate its text. So now the information label needs to be populated depending on the selected radio button.
The Welcome class
... RadioButton createRad, generateRad, buildRad, hotspotRad; Label infoLbl; public void pageCreated() { createRad = ( RadioButton )findComponent( "createRad" ); generateRad = ( RadioButton )findComponent( "generateRad" ); buildRad = ( RadioButton )findComponent( "buildRad" ); hotspotRad = ( RadioButton )findComponent( "hotspotRad" ); infoLbl = ( Label ) findComponent( "infoLbl" ); ... } public void showInfo() { NavPanel navPanel = ( NavPanel ) pageMgr.getPage( "navPanel" ); String text = ""; if ( createRad.isSelected() ) { text = "WEL_CREATE_TEXT"; navPanel.setNavPath( "wizard" ); } else if ( generateRad.isSelected() ) { text = "WEL_GEN_TEXT"; navPanel.setNavPath( "generator" ); } else if ( buildRad.isSelected() ) { text = "WEL_PACK_TEXT"; navPanel.setNavPath( "builder" ); } else if ( hotspotRad.isSelected() ) { text = "WEL_HOTSPOT_GEN"; navPanel.setNavPath( "hotspot" ); } infoLbl.setText( Translator.translate( text ) ); itemSelected = true; } ...
The pageCreated method obtains references to the components which will be used in this operation and the showInfo function sets the translated text in the label for the selected radio button. This application uses a specialized Translator class which will be explained further on.
The NavPanel class is the navigation Page which was declared last in the frames.xml file above. The setNavPath call sets the navigation path which needs to be taken for the selected radio button and that will be looked at next.
The application consists of multiple pages and navigation between pages is provided by a common class. In the screenshot below the page navigation is initiated by the buttons at the bottom right of the page.
![]() |
The NavPanel class controls the navigation of the wizard and is shown below.
The NavPanel class
public class NavPanel extends BasePage { Button prevBtn, nextBtn; BasePage currentPage; NavigationManager wizardNavigator, generatorNavigator, builderNavigator, hotspotNavigator, currentNavigator; public void pageCreated() { prevBtn = ( Button ) findComponent( "prevBtn" ); nextBtn = ( Button ) findComponent( "nextBtn" ); setCursors(); wizardNavigator = new WizardNavigator( nextBtn, prevBtn, "navigation/wizardpages" ); generatorNavigator = new NavigationManager( nextBtn, prevBtn, "navigation/generationpages" ); builderNavigator = new NavigationManager( nextBtn, prevBtn, "navigation/builderpages" ); hotspotNavigator = new NavigationManager( nextBtn, prevBtn, "navigation/hotspotpages" ); currentNavigator = wizardNavigator; } public void setNavPath( String name ) { if ( name.compareTo( "wizard" ) == 0 ) currentNavigator = wizardNavigator; else if ( name.compareTo( "generator" ) == 0 ) currentNavigator = generatorNavigator; else if ( name.compareTo( "builder" ) == 0 ) currentNavigator = builderNavigator; else if ( name.compareTo( "hotspot" ) == 0 ) currentNavigator = hotspotNavigator; } private void setCursors() { prevBtn.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); nextBtn.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); } public void prevPage() { currentNavigator.showPrevPage(); } public void nextPage() { currentNavigator.showNextPage(); } public void goHome() { currentNavigator.showHomePage(); } public void home() { goHome(); } }
The pages of this application are using a custom BasePage class which is derived from the Page. The reasons for doing this are irrelevant to this discussion. The setNavPath function which is called from the Welcome Page can be seen. This function assigns one of four NavigationManager instances to the currentNavigator variable. Each of the NavigationManagers accepts the next and previous buttons as contructor arguments along with the path to be followed. This allows the NavigationManagers to control the enabled or visible state of the buttons depending on where the current page of the application.
The navigation paths should be explained now. This is the path into some predefined nodes in the datamodel which can be seen in the wizardpages.xml file below. This page needs to be referenced from the datasource file as referred to by the ModelData startup property.
wizardpages.xml
<Datasets> <dataset id="navigation"> <dataset id="wizardpages"> <data value="welcome"/> <data value="projectinit" desc="WIZ_PROJ_SET"/> <data value="languagepage" desc="WIZ_PROJ_LANG"/> <data value="colourscheme" desc="WIZ_PROJ_LAYOUT"/> <data value="categories" desc="WIZ_PROJ_CATS"/> <data value="categorysetup" desc="WIZ_PROJ_CAT_DET"/> <data value="promptspage" desc="WIZ_PROJ_PROMPTS"/> <data value="constantspage" desc="WIZ_PROJ_CNST"/> <data value="finish" desc="WIZ_PROJ_FINISH"/> </dataset> <dataset id="generationpages"> <data value="welcome"/> <data value="projectgenerate" desc="WIZ_GEN"/> </dataset> <dataset id="builderpages"> <data value="welcome"/> <data value="projectbuilder" desc="WIZ_BUILD"/> </dataset> <dataset id="hotspotpages"> <data value="welcome"/> <data value="hotspotsetup" desc="WIZ_HTSPT_SPEC"/> <data value="hotspotedit" desc="WIZ_HTSPT_EDIT"/> </dataset> </dataset> </Datasets>
Now the NavigationManager can take care of displaying the correct page depending on whether the next or previous button was clicked.
NavigationManager class
public class NavigationManager { BaseModel navMdl; protected int currentNode = 0; BasePage currentPage; Button nextBtn, prevBtn; WizardStatus wizStatus; PageManager pageMgr; public NavigationManager( Button nextBtn, Button prevBtn, String navPath ) { Project project = ProjectManager.getCurrentProject(); pageMgr = project.getPageManager(); navMdl = ( BaseModel )project.getModel().get( navPath ); this.nextBtn = nextBtn; this.prevBtn = prevBtn; wizStatus = ( WizardStatus )pageMgr.showPage( "wizardstatus", "wizardstatus" ); setWizardPanelSize( 0 ); } public void showPrevPage() { nextBtn.setVisible(true); currentNode--; showpage(); if (currentNode == 0) prevBtn.setVisible(false); } public void showNextPage() { if ( currentPage == null ) { BaseModel mdl = (BaseModel) navMdl.get( currentNode ); String pagename = (String) mdl.get(); currentPage = (BasePage) pageMgr.getPage( pagename ); wizStatus.setNavModel( navMdl ); } if ( currentPage.checkValidations() == Validator.LEVEL_IGNORE ) { prevBtn.setVisible(true); currentNode++; showpage(); if (currentNode == ( navMdl.getNumChildren() - 1 ) ) nextBtn.setVisible(false); } } public void showHomePage() { currentPage = null; currentNode = 0; nextBtn.setVisible( true ); prevBtn.setVisible( false ); BaseModel mdl = (BaseModel) navMdl.get( currentNode ); String pagename = (String) mdl.get(); currentPage = (BasePage) pageMgr.showPage( pagename ); setWizardPanelSize( 0 ); } private void showpage() { BaseModel mdl = ( BaseModel ) navMdl.get( currentNode ); String pagename = ( String ) mdl.get(); currentPage = ( BasePage ) pageMgr.showPage( pagename ); setWizardPanelSize( 100 ); wizStatus.setCurrentItem( currentNode ); } protected void setWizardPanelSize( int width ) { Container c = wizStatus.getParent(); c.setVisible( width == 0 ? false : true ); c.getParent().doLayout(); } }
The constructor obtains a reference to the model node specified by the path parameter. This node contains all of the child nodes which define the pages for this wizard route. The constructor also obtains a reference to the wizardstatus page which is declared second in the frames.xml file. and sets its width to zero and also makes it invisible.
The showNextPage function first checks to see if the currentPage is null. If it is null it retrieves the name of the first page node in the navMdl and passes the navMdl node to the WizardStatus page. It then checks the currentpage for validations and if the validations pass the previous button is set to visible and the next page in the route is displayed. If the page displayed is the last page in the route the next button is made invisible.
The showPage function takes care is displaying the current page, making the WizardStatus page visible and passing the current node to the WizardStatus page.
The showHomePage function simply resets the variables and sets the appropriate visibility for the navigation buttons.
The WizardStatus page can now be looked at.
The WizardStatus class
public class WizardStatus extends BasePage { int currentNode = -1; BaseModel navMdl; /** Creates a new instance of WizardStatus */ public WizardStatus() { } public void setNavModel( BaseModel mdl ) { navMdl = mdl; repaint(); } public void setCurrentItem( int item ) { currentNode = item; repaint(); } public void paint( Graphics g ) { super.paint( g ); g.setColor( new Color( 136, 136, 136 ) ); if ( navMdl != null ) { int currentY = 50; for ( int i = 1; i < navMdl.getNumChildren(); i++ ) { if ( i == currentNode ) { g.setColor( new Color( 255, 58, 0 ) ); } else if ( i > currentNode ) { g.setColor( new Color( 136, 136, 0 ) ); } BaseModel itemMdl = ( BaseModel ) navMdl.get( i ); String text = Translator.translate( ModelHelper.getAttrib( itemMdl, "desc" )); g.drawString( text, 10, currentY ); currentY += 20; } } } }
The setNavModel function as referred to from the NavigationManager class can be seen taking the path node as a parameter. The setCurrentItem function stores the index of the current page in the path in a class variable. The paint function then simply iterates all of the page nodes in the navigation model and outputs the translated description of the page at intervals of twenty pixels. When the current node is reached the color of the text is changed to indicate the progress of the wizard.
One final scenario needs to be mentioned before leaving this topic. The WizardNavigator as defined in the NavPanel class is derived from the NavigationManager class and needs to do some specialized navigation. This navigation class overloads some of the NavigationManager functions in order to repeat specific pages in the navigation path. There is little need to go into the specifics of that navigation here but it's worth noting to show how custom NavigationManagers can be created.
There are several custom components included in the wizard application each designed to carry out a specific task. This section will look at a single component, the FileLocater, which can be seen in the screenshot above. There are two of these components on the first screen on the main navigation path to specify the project location and the icon path.
FileLocator constructor, setAttribute and actionPerformed methods
public class FileLocater extends JPanel implements AttributedComponent, ActionListener, TextHolder { StyleFactory fact; Label promptLbl; Edit fileEdit; Button locateBtn; public FileLocater() { setLayout( null ); fact = new StyleFactory( ProjectManager.getCurrentProject(), "org.formaria.swing" ); fact.setParentComponent( this ); promptLbl = ( Label ) fact.addComponent( "LABEL", 0, 0, 30, 20 ); fileEdit = ( Edit ) fact.addComponent( "EDIT", 30, 0, 30, 20 ); locateBtn = ( Button ) fact.addComponent( "BUTTON", 0, 0, 30, 20, "..." ); locateBtn.addActionListener( this ); } public void setAttribute( String name, Object objValue ) { String value = ( String )objValue; if ( name.compareTo( "content" ) == 0 ) setContent( value ); else if ( name.compareTo( "promptWidth" ) == 0 ) setPromptWidth( value ); else if ( name.compareTo( "dataStyle" ) == 0 ) setDataStyle( value ); else if ( name.compareTo( "style" ) == 0 ) fact.applyStyle( promptLbl, value ); } public void actionPerformed( ActionEvent ae ) { JFileChooser dlg = new JFileChooser( fileEdit.getText() ); dlg.setFileSelectionMode( javax.swing.JFileChooser.FILES_AND_DIRECTORIES); int returnVal = dlg.showOpenDialog( this ); if(returnVal == JFileChooser.APPROVE_OPTION) { fileEdit.setText( dlg.getSelectedFile().getAbsolutePath() ); } } public void setContent( String value ) { promptLbl.setText( Translator.translate( value ) ); } public void setPromptWidth( String value ) { promptLbl.setSize( Integer.parseInt( value ), promptLbl.getSize().width ); doLayout(); } public void setDataStyle( String value ) { fact.applyStyle( fileEdit, value ); }
The constructor sets the layout of the panel to null. It then constructs a new StyleFactory which will use the org.formaria.swing widget set to construct it's components. The factory is then used to create the label, text box and the locate button. An action listener is added to the button. The setAttribute function as defined in the AttribedComponent interface is called from the AriaBuilder when an unknown component attribute is found. The attributes being looked for by the component are content, promptWidth, dataStyle and style. Each of these properties are set using the component functions. It can be seen how the StyleFactory can apply a style to a component with the simple applyStyle call.
The actionPerformed function shows a JFileChooser being created with it's default path set to the text of the text component. If a new path is selected by the user the text component is once more set.
The remainder of the component can now be completed as below.
Remainder of the FileLocater class
public void doLayout() { fileEdit.setLocation( promptLbl.getSize().width, 0 ); fileEdit.setSize( getWidth() - promptLbl.getWidth() - 30, 20 ); locateBtn.setLocation( getWidth() - 30, 0 ); setOpaque( false ); super.doLayout(); } public String getText() { return fileEdit.getText(); } public void setText( String text ) { fileEdit.setText( text == null ? "" : text ); } public File getFile() { return new File( getText() ); } public Edit getEditComponent() { return fileEdit; } public void setBackground( Color c ) { super.setBackground( null ); super.setOpaque( false ); }
The doLayout function sets the location of the text and button components relative to the width of the text component and sets the panel to be transparent.
This component can now be declared in a component registration file as shown below.
components.xml
<Components> <Component class="org.formaria.quotation.FileLocater" name="FileLocater"> <Property type="set" name="content" /> <Property type="set" name="promptWidth" /> <Property type="set" name="dataStyle" /> </Component> ... <Components>
The following startup properties need to be set. The RegisteredComponentFactory will assume a filename of components.xml
startup properties
NumComponentFactories=2 ComponentFactory0=org.formaria.swing.SwingComponentFactory ComponentFactory1=org.formaria.registry.RegisteredComponentFactory
The custom component is now ready to be used within the Aria pages. This can now be done very simply by creating an element whose name corresponds to the definition of the component in the components.xml file.
ProjectInit XML page declaration
<Components> <FileLocater name="pathEdit" x="10" y="10" w="430" h="20" content="PROJINIT_PROJ_DIR" promptWidth="160" style="base/prompt" dataStyle="base/data"/> <FileLocater name="iconEdit" x="10" y="30" w="430" h="20" content="PROJINIT_PROJ_ICON" promptWidth="160" style="base/prompt" dataStyle="base/data"/> ...
The location, dimension and name attributes are used as usual and the custom attributes as expected by the setAttribute function can be seen. This example gives an impression of how the StyleManager and styles in general can be used to customize the application in general.
This application requires the use of custom validations which go beyond the abilities of the basic validations built into the Aria core libraries. This requires the use of a custom validation factory which can be specified in the startup properties file as below.
Specifying the custom validation factory
... ValidationFactory=org.formaria.quotation.validation.ValidationFactory ...
The custom validation factory is shown below.
The ValidationFactory class
public class ValidationFactory extends ValidationFactory { public ValidationFactory( Project project ) { super( project ); } public ValidationFactory( Reader reader ) { super ( reader ); } public Validator getValidation( String validationName, Method m, int mask, Object page, XmlElement pageEle ) { XmlElement ele = ( XmlElement ) validations.get( validationName ); if ( BuildProperties.DEBUG ) { if ( ele == null ) { DebugLogger.logError( "Cannot find the validation rule: " + validationName ); return null; } } String type = ele.getAttribute( "type" ); if ( type.compareTo( "integer" ) == 0 ) { IntegerValidation validator = new IntegerValidation(); validator.setName( validationName ); validator.setMask( mask ); validator.setup( ele ); return validator; } else if ( type.compareTo( "number" ) == 0 ) { NumberValidation validator = new NumberValidation(); validator.setName(validationName); validator.setMask(mask); validator.setup(ele); return validator; } else if ( type.compareTo( "function" ) == 0 ) { FunctionValidation validator = new FunctionValidation(); validator.setName( validationName ); validator.setup( ele ); return validator; } else { BaseValidator validator = ( BaseValidator ) super.getValidation( validationName, m, mask, page pageEle ); validator.setup( ele ); return validator; } } }
This custom ValidationFactory class is aware of three types of custom validations, integer , number and function . If the validation being created is none of these then the super getValidation function is called in order to create the validation and is returned. Two of the custom validations are shown below. The first:
The NumberValidation class
public class NumberValidation extends BaseValidator { public NumberValidation( Project project ) { super( project ); } public void validate( Object c, boolean forceMandatory ) throws java.lang.Exception { if ( forceMandatory ) { String text = getText(c); try { Double.parseDouble( text ); } catch ( Exception ex ) { errorLevel = LEVEL_ERROR; throwException(); } } } }
And the second:
The FunctionValidation class
public class FunctionValidation extends BaseValidator { public FunctionValidation( Project project ) { super( project ); } public void validate( Object c, boolean forceMandatory ) throws java.lang.Exception { Integer ret = ( Integer ) invokeMethod(); if ( ret.intValue() > LEVEL_IGNORE ) { errorLevel = ret.intValue(); throwException(); } } public void setup( XmlElement element ) { String function = element.getAttribute( "method" ); super.setup( element ); } }
Both classes extend the BaseValidator class and overload the validate method. The NumberValidation class simply attempts to parse the component text and if that fails the errorLevel variable of the super class is set and an exception is thrown. The FunctionValidation class overloads the setup function and retrieves the name of the method which needs to be called in order to carry out the validation.
The validations need to be declared in the validations.xml file some of which are shown below.
Validations.xml
<validation name="overwriteFile" type="function" msg="VAL_OVERWRITE_FILE"/> <validation name="rate" type="number" msg="VAL_QTY_NUM" mandatory="true"/> ...
And the validation rules themselves are as follows:
The validations defined in a page
<Validation rule="rate" target="taxEdit"/> <Validation rule="overwriteFile" target="finishBtn" method="fileExists"/>
The rate validation checks that the text entered into the taxEdit text component is numeric. The overwriteFile function calls the fileExists function in the Page as shown below.
fileExists function
public Integer fileExists() { File f = new File( getPath() + File.separatorChar + "xlintquote.xls" ); return new Integer( f.exists() ? BaseValidator.LEVEL_WARNING : BaseValidator.LEVEL_IGNORE ); }
The fileExists function checks for the existence of a named file and if exists the BaseValidator LEVEL_WARNING is returned in an Integer object. If it does not exist the LEVEL_IGNORE value is returned.
One of the functions of the BasePage class is to set a custom ExceptionHandler for each of the pages as shown below.
BasePage constructor
public class BasePage extends Page implements ModelListener { public BasePage() { setExceptionHandler( new ExceptionHandler( this ) ); }
The custom exception handler follows. The ExceptionHandler class
public class ExceptionHandler implements ExceptionHandler { boolean pageValidation = false; String validationText = ""; private Page currentPage; Vector errors, warnings; public ExceptionHandler( Page page ) { currentPage = page; } public boolean handleException( Object comp, Exception ex, Object xvalidator ) { Validator validator = ( Validator ) xvalidator; if ( ( validator.getLevel() == validator.LEVEL_ERROR ) && ( ! pageValidation ) ){ currentPage.showMessage( "Input error", ex.getMessage() ); return true; } String msg = validator.getMessage(); if ( validationText.length()>0 ) validationText += "\n"; validationText += msg; if ( validator.getLevel() == validator.LEVEL_ERROR ) errors.add( msg ); else warnings.add( msg ); return true; } public int accumulateMessages( boolean start, int level ) { pageValidation = start; if ( pageValidation ){ errors = new Vector(); warnings = new Vector(); validationText = ""; } else { if ( validationText.length()>0 ) { if ( level == Validator.LEVEL_WARNING ) { ErrorMessage dlg = new ErrorMessage( errors, warnings ); dlg.setSaveOnClose( false ); int r = dlg.showDialog( currentPage ); return r == 0 ? 0 : level; } else { ErrorMessage dlg = new ErrorMessage( errors, warnings ); dlg.setSaveOnClose( false ); dlg.showDialog( currentPage ); return BaseValidator.LEVEL_ERROR; } } } return level; } }
The accumulateMessages function is called when the validations for a page begins and is passed true in the start parameter. This value is recorded in the pageValidation variable. The handleException function is called whenever a validation triggers a problem and depending on value returned from the getValue function of the Validator object the validation message is added to the appropriate Vector. When all of the validations are finished the accumulateMessages function is called once more and this time the start parameter is false . In this case an Dialog ErrorMessage is created in two possible ways. If there are errors the messages will be displayed with only a close button. If there are only warnings generated then the dialog will give the option of cancelling or continuing, disregarding the warnings. This functionality can be seen in the value returned from the showDialog function. This will be explored in more detail in the section on dialogs.
The custom FileLocater component needs to have several validations applied to it as shown below.
The FileLocator validations
... <Validation rule="projectpath" target="pathEdit"/> <Validation rule="projectexists" target="pathEdit" method="projectExists"/> <Validation rule="projectfolder" target="pathEdit" method="projectFolder"/> <Validation rule="folderexists" target="pathEdit" method="folderExists"/> ...
These validations are declared in the validations file as below.
The validations.xml declarations
... <validation name="projectpath" type="mandatory" msg="VAL_PATH_CANNOT_EMPTY"/> <validation name="projectexists" type="function" msg="VAL_PATH_NOT_EMPTY"/> <validation name="projectfolder" type="function" msg="VAL_NOT_DIR"/> <validation name="folderexists" type="function" msg="VAL_PATH_NOT_EXIST"/> ...</P> </TD> </TR> </CAPTION> </TABLE> <P> The <EM> projectPath</EM> is a simple built in mandatory validation and the functions for the <EM> FunctionValidations</EM> follows.</P> <p><i> The FunctionValidations functions</i></p><java> public Integer projectExists() { File f = pathEdit.getFile(); boolean ret = ( f.list() == null ) || ( f.list().length == 0 ); return new Integer( ret ? BaseValidator.LEVEL_IGNORE : BaseValidator.LEVEL_WARNING ); } public Integer folderExists() { return new Integer( pathEdit.getFile().exists() ? BaseValidator.LEVEL_IGNORE : BaseValidator.LEVEL_ERROR ); } public Integer projectFolder() { return new Integer( pathEdit.getFile().isDirectory() ? BaseValidator.LEVEL_IGNORE : BaseValidator.LEVEL_ERROR ); }
The data bindings for the wizard application are on the whole pretty straightforward and there are some which deserve particular attention.
The projectinit page
<Components> <Panel constraint="Center" painter="org.formaria.swing.LogoBackground" style="synthPanelLight"> <Label x="20" y="10" w="500" h="30" style="base/title" content="PROJINIT_TITLE" opaque="false"/> <Panel x="100" y="100" w="450" h="250" border="1" painter="org.formaria.swing.LogoBackground" style="synthPanelLight"> <FileLocater name="pathEdit" x="10" y="10" w="430" h="20" content="PROJINIT_PROJ_DIR" promptWidth="160" style="base/prompt" dataStyle="base/data"/> <FileLocater name="iconEdit" x="10" y="30" w="430" h="20" content="PROJINIT_PROJ_ICON" promptWidth="160" style="base/prompt" dataStyle="base/data"/> <Label x="10" y="50" w="160" h="20" style="base/prompt" content="PROJINIT_PROJ_TITLE"/> <Edit name="titleEdit" x="170" y="50" w="240" h="20" style="base/data"/> <Label x="10" y="70" w="160" h="20" style="base/prompt" content="PROJINIT_PROJ_WEL_TITLE"/> <Edit name="welcomeTitleEdit" x="170" y="70" w="240" h="20" style="base/data"/> <Label x="10" y="90" w="160" h="20" style="base/prompt" content="PROJINIT_PROJ_WEL_MES"/> <ScrollPane x="170" y="90" w="240" h="100"> <TextArea name="welcomeText" x="0" y="0" w="250" h="100" style="base/data"/> </ScrollPane> <Label x="10" y="200" w="160" h="20" style="base/prompt" content="PROJINIT_PROJ_WEL_INFO"/> <Edit name="infoEdit" x="170" y="200" w="240" h="20" style="base/data"/> </Panel> </Panel> </Components> <Data> <Bind target="pathEdit" source="/project/projectPath" class="org.formaria.quotation.data.FileLoaderBinding"/> <Bind target="iconEdit" source="/project/projectIcon" class="org.formaria.quotation.data.FileLoaderBinding"/> <Bind target="titleEdit" source="/project/projectTitle"/> <Bind target="welcomeTitleEdit" source="/project/welcomeTitle"/> <Bind target="welcomeText" source="/project/welcomeMessage"/> <Bind target="infoEdit" source="/project/companyInfo"/> </Data>
The last four bindings are straightforward text bindings and are bound to text components. The first two bindings are bould to the custom FileLocator components created earlier. The type attribute specifies the use of a custom binding and the class attribute provides the name of the class which will carry out the binding. The FileLoaderBinding is shown below.
FileLoaderBinding class
public class FileLoaderBinding extends TextBinding { public FileLoaderBinding() { } public void setup( Project project, Object c, Hashtable bindingConfig, Hashtable instanceConfig ) { comp = ( ( FileLocater ) c ).getEditComponent(); srcPath = ( String )instanceConfig.get( "source" ); BaseModel srcModel = ( BaseModel ) project.getModel().get( srcPath ); outputPath = DataModel.prefixOutputPath( srcPath ); if (( srcModel == null ) && ( srcPath != null )) sourceModel = ( DataModel )project.getModel().get( srcPath ); else sourceModel = srcModel; comp = c; attribStr = ( String )instanceConfig.( "attrib" ); } }
The custom binding extends the TextBinding class and implements the setup function. The passed component is cast to a FileLocater component and the text component is retrieved from it.
The wizard allows the user to create applications with whatever languages they require. This is done through the languages page on the main wizard path.
![]() |
The master list component contains a list of all of the languages which are available from the wizard. The selected languages list contains a list of languages which have been selected to date. The master list is populated from a predefined set of languages defined in a datasets file as shown below.
languages.xml
<Datasets> <dataset id="languagelist"> <lang id="Chinese" res="LST_LANG_ZH" code="zh" requiresutf="yes" locallang="æ±?è¯"/> <lang id="English" res="LST_LANG_EN" code="en" include="true" locallang="English"/> <lang id="French" res="LST_LANG_FR" code="fr" locallang="Français"/> <lang id="German" res="LST_LANG_DE" code="de" locallang="Deutsch"/> </dataset> <dataset id="wizardlanguages"> <lang id="English" value="English" code="en"/> <lang id="æ±?è¯" value="æ±?è¯" code="zh" encoding="UTF-8"/> </dataset> </Datasets>
This datasets definition show how the model can be used to store much more than just ids and values for a node. In the languageList node each language node has information about the localized language name the language code and whether the language relies on Unicode.
In the LanguagePage class the master list is populated in the code below.
Some of the LanguagePage class
List masterLst, languageLst; LanguageModel masterListModel, listModel; Checkbox defaultChk; public void pageCreated() { langListMdl = ( BaseModel ) rootModel.get( "languagelist" ); languageLst = ( List ) findComponent( "languageLst" ); masterLst = ( List ) findComponent( "masterLst" ); defaultChk = ( Checkbox ) findComponent( "defaultChk" ); listModel = new LanguageModel( "selectedLanguages" ); languageLst.setModel( listModel ); masterListModel = new LanguageModel(); masterLst.setModel( masterListModel ); populateMasterLangs(); populateDefaultLangs(); updateLists(); } private void populateMasterLangs() { for ( int i = 0; i < langListMdl.getNumChildren(); i++ ) { BaseModel langMdl = ( BaseModel ) langListMdl.get( i ); String include = ModelHelper.getAttrib( langMdl, "include" ); if ( ( include == null ) || ( include.compareTo( "true" ) != 0 ) ) { masterListModel.addElement( langMdl ); } } } private void populateDefaultLangs() { for ( int i = 0; i < langListMdl.getNumChildren(); i++ ) { BaseModel langMdl = ( BaseModel ) langListMdl.get( i ); String include = ModelHelper.getAttrib( langMdl, "include" ); if ( ( include != null ) && ( include.compareTo( "true" ) == 0 ) ) { listModel.addElement( langMdl ); } } } private void updateLists() { masterLst.updateUI(); languageLst.updateUI(); }
The pageCreated method shows references being obtained to the page components which will be used in the class. New LanguageModel classes are created which are used as models for each of the lists. Taking the masterListModel , the populateMasterLangs function iterates all of the languages in the languagelist dataset and adds those elements whose include attribute is set to false . The converse is done for the listModel within the populateDefaultLangs function. This means that English will appear by default in the selected languages list when the page first appears. The updateLists function needs to be called whenever the list data changes so as to render the changes correctly. The LanguageModel class follows.
The LanguageModel class
public class LanguageModel implements ListModel { BaseModel langMdl; Project project; public LanguageModel() { langMdl = new BaseModel(); project = ProjectManager.getCurrentProject(); } public LanguageModel( String path ) { langMdl = ( BaseModel ) project.getModel().get( path ); } public void setDefault( String name ) { ModelHelper.setAttrib( langMdl, "default", name ); } public String getDefault() { return ModelHelper.getAttrib( langMdl, "default" ); } public void addElement( Object ele ) { langMdl.append( ( BaseModel ) ele ); } public void removeElement( Object ele ) { langMdl.remove((BaseModel)langMdl.get(((Integer)ele).intValue())); } public BaseModel getModelAt( int i ) { return ( BaseModel )langMdl.get( i ); } public int getSize() { return langMdl.getNumChildren(); } public Object getElementAt( int i ) { BaseModel mdl = ( BaseModel )langMdl.get( i ); String res = ModelHelper.getAttrib( mdl, "res" ); return Translator.translate( res ); } public void addListDataListener( ListDataListener listener ){} public void removeListDataListener( ListDataListener listener ){} }
The LanguageModel class implements the ListModel interface and as such needs to implement the required methods such as addElement and removeElement . This class is now effectively acting as a wrapper for the language models and makes it easy to move the model nodes around. This class also takes care of the default language and uses the Translator object to translate the language names to the current application language.
The application has been completely translated into Simplified Chinese and required some specific techniques to take account of the Unicode characters needed to render the language correctly. The results of that localization can be seen below.
![]() |
The default language for the wizard is English and that is specified in the startup properties as follows.
The language startup property
... Language=en ...
This means that the text for the language will be stored in a file called en.properies some of which is shown below. For each page in the application which is to be localized automatically by Aria the resource attribute needs to be set in the page XML.
The Page resource attribute
<Page resource=""> ...
Section of the en.properties file
... WIZ_PROJ_PROMPTS=Prompts PROMPT_DESC=Description\: DATA_LST_NAV_CFG=Configurator navigation DATA_LST_SCR_DRILL=Simple drilldown WEL_HOTSPOT_GEN=Create or edit a hotspot file COL_APP_TYPE=Application type\: PROJ_BUILD_LOC=Locate the spreadsheet you wish to generate WEL_CLICK_CONT=Click the next button to continue ...
This is a typical resource file for a Java application.
The language list on the welcome page is populated from the wizardlanguages dataset declared in the languages.xml file which was shown in the previous section.
Some of the welcome.xml page
... <Panel laf="true" name="langPanel" x="0" y="0" w="200" h="30" border="0" style="header/light" opaque="false"> <Label x="5" y="5" w="80" h="20" style="welcome/prompt" content="WEL_LANG" opaque="false"/> <Combo name="langlist" x="85" y="5" w="100" h="20" style="welcome/data/utf"/> </Panel> ... <Events> <Event target="langlist" type="FocusHandler" method="changeClicked"/> <Event target="langlist" type="ActionHandler" method="changeLanguage"/> ... <Data> <Bind target="langlist" source="wizardlanguages" output="wizardlanguages"/> </Data> ...
The langlist combobox is bound to the wizardlanguages node of the data model and will appear with a list of its children from which the user can select. There are two events attached to the language list changeClicked and changeLanguage and these are shown below.
Some of the WelcomePage class
... ComboBox langlist; boolean changeClicked = false; public void changeClicked() { changeClicked = true; } public void changeLanguage() { if ( changeClicked ) { String selected = (String) langlist.getSelectedObject(); BaseModel mdl = (BaseModel)rootModel.get( "wizardlanguages/" + selected ); String langCode = ModelHelper.getAttrib( mdl, "code" ); String langEncoding = ModelHelper.getAttrib(mdl, "encoding"); project.setStartupParam("Language", langCode); Translator.reset( langEncoding == null ? false : true ); pageMgr.reset(); pageMgr.showPage( "welcome", "content" ); pageMgr.showPage( "navPanel", "navPanel" ); pageMgr.showPage( "banner", "banner" ); } } ...
The changeClicked boolean variable is set to true only when the user clicks on the languages combobox. This ensures that the code in the changeLanguage function is not executed as the list in populated by the binding. If the variable is true and the user selects a new language then the changeLanguage code is invoked. The selected language is obtained from the list and the corresponding language node is retrieved from the model. The code and encoding attributes are then retrieved from the language node. The Language startup parameter is then set for the application and the Translator is reset to use language encoding or not. Now the page manager is reset and the startup pages are reloaded into their respective target frames.
At this point its worth looking at the Translator object and how it manages the necessary fonts.
The Translator Singleton class
public class Translator { protected static ResourceBundle languageResourceBundle; private static Translator translator; private static boolean UTF_Encoding = false; private static String defaultFont = "NSimSun"; private Translator() { Project proj = ProjectManager.getCurrentProject(); languageResourceBundle = proj.getResourceBundle( proj.getStartupParam( "Language" )); } public static Translator getInstance() { if ( translator == null ) translator = new Translator(); return translator; } public static String translate( String key ) { if ( languageResourceBundle == null ) getInstance(); if (( key != null ) && ( languageResourceBundle != null )) { try { String trans = languageResourceBundle.getString( key ); if ( trans != null ) return trans; } catch ( Exception ex ) { if ( BuildProperties.DEBUG ) DebugLogger.logWarning( "No translation found for the key: " + key ); } } return key; } public static void reset() { reset( false ); } public static void setUseUTF( boolean use ) { UTF_Encoding = use; } public static void reset( boolean useUTF ) { translator = null; getInstance(); translator.setUseUTF( useUTF ); UTF_Encoding = useUTF; } public static boolean useUTF() { return UTF_Encoding; } public static String getDefaultFont() { return defaultFont; } }
The Translator is a singleton class. The call to the reset function with the useUTF parameter informs it whether or not the application is running with a language which requires Unicode encoding. If so the NSimSun font is returned from the getDefaultFont function. This font could just as easily be parameterized. Now each of the StyleFactory in each of the Pages needs to make use of the Translator class in order to construct its components with the correct font so a new StyleManager class is introduced which extends the basic StyleManager class as shown below.
The StyleManager class
public class StyleManager extends org.formaria.style.StyleManager { public Font getFont( String style ) { Font f = super.getFont( style ); if ( Translator.useUTF() ) { f = new Font( Translator.getDefaultFont(), f.getStyle(), f.getSize() ); } return f; } public Font getFont( Style style ) { Font f = super.getFont( style ); if ( Translator.useUTF() ) { f = new Font( Translator.getDefaultFont(), f.getStyle(), f.getSize() ); } return f; } }
The custom StyleManager class overloads the two getFont functions of the base class and checks the Translator object to see if Unicode is being used. If it is then the Translator's default font is used to construct a new font with the same style and size as specified in the styles file. It's now simply a matter of telling the Aria to use this style manager which is done through the startup properties as follows.
The DefaultStyleManager startup property
... DefaultStylemanager=org.formaria.quotation.ui.StyleManager ...
The look and feel of the application which can be see from the screenshots in the preceding sections is achieved through use of synth. This look and feel is only available since JDK 1.5
In order to specify the use of the synth look and feel some startup parameters needs to be set as follows.
Synth look and feel startup properties
... LAF=org.formaria.laf.SynthLafInstaller SynthConfigFile=aria.xsynth SynthResourceLoader=org.formaria.controls.swing.Welcome ...
In order to use the look and feel for panels a painter attribute needs to be specified as follows. Specifying the synth logo painter for panels
<Panel x="100" y="80" w="390" h="320" border="1" painter="org.formaria.swing.LogoBackground" style="synthPanelLight"> <Panel x="5" y="5" w="160" h="150" border="1" painter="org.formaria.swing.LogoBackground" style="synthPanelLight"> <Label x="5" y="5" w="80" h="20" style="base/prompt" content="LANG_MASTER"/> <List name="masterLst" x="5" y="25" w="150" h="90" style="base/data"/> <Button name="addBtn" x="5" y="120" w="150" h="25" content="PROMPT_ADD" style="base/data"/> </Panel> <Panel x="170" y="5" w="210" h="150" border="1" painter="org.formaria.swing.LogoBackground" style="synthPanelLight"> <Label x="5" y="5" w="125" h="20" style="base/prompt" content="LANG_OTHER"/> <Label x="5" y="35" w="110" h="20" style="base/prompt" content="LANG_CODE"/> <Edit name="languageCode" x="120" y="35" w="80" h="20" style="base/data"/> ...
The aria.synth file needs to be available on the classpath and in this case was placed in the resources folder. The aria.synth file specifies component borders and offsets and also refers to images which it uses to render the components. For example it refers to the following two images to render the buttons. The orange version is for mouse over events giving a very interactive application.
![]() |
CoolSelector is a web-based tool for designing cold-room configurations. It was designed and built for Danfoss Refrigeration and Air Conditioning Controls group to allow their consultants, wholesalers and installers to select and configure all the components and controls needed to meet the cold-room requirements of their customers.
Originally, this sophisticated internet application was designed and coded using a mix of Microsoft tools (ASPs, COM, HTML and JavaScript). Later, we rebuilt the application using Formaria's Aria, resulting in a more streamlined tool with faster response times.
Danfoss A/S, the leading supplier of Air Conditioning and Refrigeration components and controls, asked Formaria to develop a configuration tool to allow them to design cold-rooms for their customers. Cold-rooms are typically used by supermarkets and manufacturing/processing plants to ensure that produce is held at a low enough temperature throughout its storage/processing. They needed a system that would allow them to select and configure all of the components and controls needed to build the cold-rooms. The design tool would be used by consultants, wholesalers and installers who would access Danfoss's website to order the cold-room kit on-line.
Danfoss had a clear picture of their client-side requirements, including an interactive graphical user interface to allow quick and simple selection of components.
Danfoss requested that this application be developed as ASPs. We used COM/DLLs objects for the core selection engine to handle the complex mathematical calculations involved in the physical modelling aspect of the system.
The system, known as `CoolSelector' was launched to much acclaim at the IKK show in 2002 and has already spawned a number of imitations.
In developing CoolSelector, we maintained a clean separation between the underlying calculation model and the user interface. This separation was to allow the system to change and expand during its lifetime and to support a variety of client interfaces including a standalone, off-line version.
CoolSelector and its underlying data model provided much of the inspiration for Aria
The original implementation of CoolSelector uses a mix of Microsoft technologies: the client application is a set of HTML pages with extensive JavaScript support to provide interaction. The front end communicates with a server by means of an XML calling-mechanism, reminiscent of the SOAP protocol.
As a means of reducing the complexity of the JavaScript and supporting the XML data-exchange and calling-convention, a Java Applet (running within IE's JVM) is used on the client-side.
On the server-side, a set of ASPs provide integration with back-end corporate functions, such as user identification, localization and order processing. The ASPs employ a number of COM objects for input validation, database access, file access and functions such as unit conversion (metres to inches etc...).
Core calculations are implemented in C++ and rely on a SQL Server database and several further COM objects for XML access.
In all, the system comprises HTML, JavaScript, XML, ASPs, VB, Java, C++ and SQL and is deployed on a Microsoft IIS server.
The core design works well and has allowed several extensions and changes to the system to be implemented. However, during development and since deployment we identified several areas with room for improvement.
There are two main areas where improvements could be made to CoolSelector: firstly the coding, and secondly the deployment.
To some extent both of these problems relate to the use of COM objects and the types of interfaces they expose. From a coding perspective, the level of access to XML and databases is too low, resulting in the use of more "plumbing" code than is desirable. From a deployment perspective the use of COM objects means having to stop and start servers and well known DLL-compatibility issues.
To a lesser extent, the design trade-offs gave rise to several problems. Firstly, using an HTML front-end requires extensive JavaScript coding to provide the required degree of interactivity: JavaScript can be difficult to maintain. Secondly, processing and calculations that could have easily been carried out on most desktop computers were instead carried out on the server with an associated time delay.
Initially, the system was intended to support a variety of browsers, but over time support issues and difficulties with subtle differences in the integration of JavaScript and the underlying object model meant that CoolSelector became an IE-only system.
Given all the above, CoolSelector Mark 1 neared the limits of what can be accomplished with an HTML-based front end.
The Aria version of CoolSelector started as an experiment to see how Aria performed in the development of a rich/complex client application.
Encouraged by initial successes, we began to add new data-access features to CoolSelector using the Aria data model. Aria's data binding facilities immediately meant that a whole layer of data manipulation disappeared: instead of parsing responses and updating controls, Aria allows controls to be bound to data with a simple declaration. For CoolSelector, this meant that we could update all input fields by simply declaring the data bindings. Furthermore this binding removed another layer of custom coding used to coordinate data between pages. These advantages alone provide a persuasive argument for using Aria.
Aria also greatly simplified the construction of the user interface: HTML was not designed for the creation of interfaces like CoolSelector's and to render CoolSelector's complex interface hundreds of lines of HTML in addition to hundreds of lines of JavaScript were required. By comparison, the Aria interface requires only tens of lines of code. From a maintenance point of view, the importance of this code-length reduction cannot be underestimated: less code means less maintenance!
Next, we decided to try out some core calculations in the Aria version, and the results were astounding. Again the Aria approach paid dividends as we were able to remove large amounts of plumbing code (for XML, Database access and marshalling of server requests and responses). However, the performance impact was the biggest surprise!
We had expected that using Java in place of C++ for the intensive calculation operations needed would result in a sluggish system, but in fact, we found that un-optimized selections happened almost instantaneously instead of in the 20-30 seconds we had grown accustomed to!
To an extent, the results achieved are implicit in using a rich client as opposed to a thin (HTML) client. After all, the rich client provides locality of computing, one of the cornerstones of computing performance.
Perhaps the biggest win comes from the simplification of the architecture and code. Aria takes care of most of the plumbing so the application-specific code need only include the explicit business logic required. Isolating this business logic makes for easy coding and maintenance.
The Aria system comprises Java, XML and some SQL, all in all a lot simpler than the client architecture. Gone are the COM objects, simplifying the server deployment dramatically.
The gains do not stop there! Implicit in the Aria architecture is the ability to operate in an occasionally-connected environment, and therefore an off-line/standalone version is a given rather than an additional requirement. Automatic update and synchronization of an off-line version is also implicit. Therefore, a Aria CoolSelector user will see little difference whether they are on- or off-line.
From a corporate standpoint, Aria CoolSelector can still run within IE's JVM. This means that it can appear just as much a part of the corporate website as any other pages. Rebranding can be achieved by editing a single configuration file and a whole gamut of extensibility and customization features are at hand. It is easy to see how Aria as an inherently open architecture could form the basis of an industry platform.
Direct comparison between the Aria and HTML versions of CoolSelector may be inappropriate as some of the code has been replaced by library code and some gains are implicit in creating a rich client application instead of an HTML client. That said, the Aria version of the application represents a 75% reduction in code and approximately a 50% saving in the number of subsystems used.
Selections with Aria occur in 1-2 seconds, compared to 20-30 seconds with HTML/COM on the same machine - a staggering 3000% improvement in performance! In a multi-tier/multi-node server environment, this would translate to almost a complete removal of the work load. From an application-development point of view, the performance gain is significant in that it opens up the possibility of adding features such as simulation and diagnostic capabilities. Significantly, the Aria version of CoolSelector implicitly includes an updatable, standalone client-application as one of the many other functionality gains. Not only does this make CoolSelector accessible to a wider audience but it also makes it feasible to use CoolSelector on a broader range of devices (WebPads/Tablets/PDAs) some of which may be used by field workers such as service technicians.
CoolSelector has many advanced features. First of all it simplifies the task of choosing a coldroom system for a specific market niche. In the screenshot below you can see the main configuration options on the left hand side. A refrigeration engineer would be more than familiar with these settings and indeed some of these settings such as the voltages would be regional specific.
CoolSelector will remember the user preferences so that once the sytem has been used there is probably no need to set these options very often. Selecting a system then becomes a matter of choosing the appropriate options and entering some operating conditions.
Remember CoolSelector is built using AWT and with just the components built into Aria..
Notice how the settings page contains a language button. This button pops up a dialog where you can choose the language in which the application is displayed. The localization of CoolSelector uses the very same techniques as described in this manual, so you can see that is possible to translate a complete application using the simple methods we described. Here's the same page in German.
CoolSelector featues an interactive way of setting the load. Either the users can enter the data on the top half of the page in a form or they can setup the load interactively by clicking on various parts of the graphic at the bottom. Rules and validations are applied to both.
The beauty of a rich-client application is its ability to take advantage of local computing power and CoolSelector does this to great effect. Load calculations and selections are instantaneous and this is a vast improvement over web based systems. The ease with which the system can be programmed also lends itself to adding more powerful and compelling user interaction.
CoolSelector can work on-line or off-line. In an on-line mode CoolSelector will check for connectivity and provide extra product information from Danfoss's corporate website.
In the screenshot below some information about a component is being displayed.
The Web button provides a link to the Internet usings the JDIC web browser component.
Ultimately the application is an e-commerce application and we get a list of part numbers ready for ordering.
For off-line users and for project mangement purposes CoolSelector also produces detailed summary information that can be saved for quality control purposes.
The summary information can even be exported and printed. The export features include export to office formats so that the selection can be reported as part of a larger document, for example a design specification or as an offer document to a prospective client.
And for the techies in the business there are even diagrams showing the characteristics of the proposed systems operation: