You are hereAria User Guide / Section III: In Depth / POJOs
POJOs
As we have seen earlier Aria supports a number of ways of working with POJOs, Hibernate and other data structures either within the editor using drag and drop or through the use of customized builders. Aria also supports the dynamic creation of forms as runtime with little or no special configuration
Introducing PojoPanel
PojoPanel
is a panel that takes a reference to a POJO and constructs a user interface for that component based upon the POJO's public properties and methods. The panel configures the data bindings and event handlers for the UI automatically. In declaring an instance of the panel it is possible to provide layout hints for things like the number of columns, spacing, field order, exclusion of fields and more.
The
PojoPanel
can be used with POJOs from both Hibernate and Spring as well as other sources.
For example the following code binds to a POJO:
A sample page using a PojoPanel
<?xml version="1.0" encoding="UTF-8"?> <Page w="1016" h="734" class="com.myproject.MyController"> <Components> <Panel title="My Form" border="0" layout="border"> <PojoPanel name="myForm" constraint="center" style="base" pad="6" spacing="4" numCols="2" dataPath="pojo/myModel/myDataObject" methods="save" class="com.myproject.transfer.MyDataObject" fieldOrder="a,c,d,b,f"/> <Panel constraint="south" layout="flow" align="center" style="base" hgap="6" vgap="6"> <Button name="saveBtn" content="Save"/> </Panel> </Panel> </Components> <Events> <Event target="saveBtn" method="${pojo/myModel.saveStuff(pojo/model/myDataObject)}" type="org.formaria.pojo.SaveBeforeActionHandler"/> </Events> </Page>
where the page is setup as normal and the class
com.myproject.MyController
is the normal
Page
implementation. The key component is the
PojoPanel
declaration. Here the fields have the following roles:
|
Attribute |
Role |
|---|---|
|
pad |
gives the spacing around the content/columnar data |
|
spacing |
sets the spacing between the items that are placed on the panel |
|
numCols |
sets the number of columns (labels and input) used by the panel |
|
dataPath |
refers to the object instance that is used as the source for the data bindings |
|
class |
the class whose fields and methods are used in the panel |
|
methods |
corresponds to the names of the methods on the class referred to by the class attribute that are linked to the panel |
|
fieldOrder |
an override for the order in which the fields are displayed |
|
fieldValidationRule |
is the name of the validation rule for the input fields |
|
excludes |
(not shown above) a list of fields not to include in the display |
The event handling follows a similar rationale with the method being declared as an evaluated attribute. Aria 4.0 introduces new evaluated attribute support whereby the data model nodes can be referenced and the methods of those objects invoked. In the above setup the
saveStuff
method of the data model object at the path pojo/myModel is invoked. The method takes one argument and again this is a data model object - in the example it corresponds to the POJO used by the PojoPanel. The event adapter
SaveBeforeActionHandler simply calls saveBoundComponentValues
beofre the POJO's method is invoked.
In populating the panel the PojoPanel inspects the POJO looking for properties (as defined by the Java Bean specification) and methods (like the save method above). If a method with just a getter is found then it is assumed to be read-only.
When a property is found the PojoPanel sets up the data bindings to the appropriate POJO getter and setter methods. In addition the PojoPanel checks to see if a vocabulary has been defined for the field by checking for the existence of a model node at
vocab/<<[field name]>>. If such a vocabulary is found it is assumed that the field value is part of a pick list and a drop down list/combo is displayed for that component. The PojoPanel also inspects the type of each property and tries to assign input fields of the appropriate type, for example a DateEdit input is created for a date property.
Generally the field or property name is not appropriate for display as a label or prompt and therefore the
PojoPanel looks up the language files for a translation of the property name.
The above example is rendered as:
Later we will add an additional configuration file to control how the panel is rendered. The configuration will be based upon the options available within the Aria editor when creating panels from POJOs with drag and drop.
Building the Panel
In populating the panel the PojoPanel inspects the POJO looking for properties (as defined by the Java Bean specification) and methods (like the save method above). If a method with just a getter is found then it is assumed to be read-only.
Customizing the View
In some case not all the POJO fields will be required for a form, or it may be desireble to present the POJO in different ways to different users and therefore it is possible to customize the behavior with a view. The view can be loaded in two ways, either through and XML configuration file or through a data structure held within the data model.
The file structure used by the view is as follows:
Basic view file format
<View> <Properties enabledByDefault="true" includeByDefault="false" fieldValidationRule="pojoFields"/> <Field name="fieldname1" visible="true" enable="true" exclude="false" type="Edit" /> <Field name="fieldname2" visible="true" enable="true" exclude="false" type="Edit" /> ... <Field name="fieldnameN" visible="true" enable="true" exclude="false" type="Edit" /> </View>
The fields are declared in the order in which they should appear on the form and the Field element can include an arbitrary number of attributes.
Alternatively the data can be stored in an XmlElement within the data model. When stored in this way an adapter can be used to construct the data structure, adapting the field values as needed, for example
Adapting a model element to the internal view format
public class ViewAdapter { public static XmlElement adapt( List<ViewDTOElement> view ) { Hashtable<String,XmlElement> viewFields = new Hashtable<String,XmlElement>(); int numFields = view.size(); NanoXmlElement parentNode = new NanoXmlElement( "View" ); NanoXmlElement[] children = new NanoXmlElement[ numFields ]; int idx = 0; for ( ViewDTOElement entry : view ) { nanoXmlElement element = new NanoXmlElement( "field" ); children[ idx ] = element; element.setAttribute( "name", entry.getId()); String visibility = entry.getEvisibability(); boolean enabled = !"readonly".equalsIgnoreCase( visibility ); boolean visible = "editable".equalsIgnoreCase( visibility ) || !enabled; element.setAttribute( "visible", Boolean.toString( visible )); element.setAttribute( "type", entry.getEtype() ); element.setAttribute( "label", entry.getId() ); element.setAttribute( "enabled", Boolean.toString( enabled )); element.setAttribute( "exclude", Boolean.toString( "private".equalsIgnoreCase( visibility ))); element.setAttribute( "order", Integer.toString( idx++ )); } for ( NanoXmlElement child : children ) parentNode.addChild( child ); return parentNode; } }
PojoPanels and Dialogs
PojoPanels
can be nested within dialogs using the
PojoDialog
class. For example:
Create a dialog from the Generic PojoDialog
EditAccountDetails accDetailsDialog = (EditAccountDetails)PojoDialog.createDialog( EditAccountDetails.class, PojoDialog.READ_ACTION, "pojo/currentAccountDTO", "Account details" ); accDetailsDialog.setLogo( "hands.gif" ); accDetailsDialog.setViewFile( "views/accounts" ); accDetailsDialog.showDialog( this ); int status = Dialog.getLastReturnValue(); if ( status == Dialog.OK_CLICKED ) { ... }
The above creates a dialog for the pojo, nesting a panel within the dialog and providing some basic CRUD operation support. The dialog uses a
PojoDialogTemplate.xml
file so that the appearance of the dialog can be fine tuned for individual applications.
Data Binding
When a property is found the PojoPanel sets up the data bindings to the appropriate POJO getter and setter methods. In addition the PojoPanel checks to see if a vocabulary has been defined for the field by checking for the existence of a model node at
vocab/<<[field name]>>. If such a vocabulary is found it is assumed that the field value is part of a pick list and a drop down list/combo is displayed for that component. The PojoPanel also inspects the type of each property and tries to assign input fields of the appropriate type, for example a DateEdit
input is created for a date property.
The alwaysDirty attribute controls how the paths are evaluated. Normally the PojoModel nodes cache their data and do not invoke the POJO method each time they are accessed. If you are trying to binding to a value that might change, such as a current user or a details node in a master-detail relationship then the underlying object might change and therefore the cache becomes out of sync. Setting the alwaysDirty
attribute means that the POJO method is always evaluated and it should therefore return the correct value in such a case.
Event Handling
The event handling follows a similar rationale with the method being declared as an evaluated attribute. Aria 4.0 introduces new evaluated attribute support whereby the data model nodes can be referenced and the methods of those objects invoked. In the above setup the saveStuff method of the data model object at the path pojo/myModel is invoked. The method takes one argument and again this is a data model object - in the example it corresponds to the POJO used by the PojoPanel. The event adapter SaveBeforeActionHandler
simply calls saveBoundComponentValues beofre the POJO's method is invoked.
Prior to Aria 4.0 it was not possible to access the methods of a POJO via Aria's event handling and this meant that the page class had to contain a proxy method to invoke method on the POJO. As of version 4.0 POJO methods can be invoked by specifying them as any other event
Adding a POJO event handler
<Events> <Event target="saveBtn" method="${pojo/myModel.saveStuff(pojo/model/myDataObject)}" type="org.formaria.pojo.SaveBeforeActionHandler"/> </Events>
when the event handler finds a slash (/) in the method specification it assumes it is dealing with a model node. Using this approach the path pojo/myModel is assumed to be a model node wrapping a POJO which contains the method saveStuff. The method may take arguments which in turn can point to model nodes. In the above example the argument pojo/model/myDataObject is also assumed to be a model node.
As the event handling declarations are processed when the page is loaded the event handler stores a reference to the model model implementing the method and also the model nodes providing the arguments.
During the life of an application the content of the model nodes may change and therefore the event handler does not normally get the content of the node till it needs to invoke the target method. However during setup the event handler gets the POJO wrapped by the model node so as to determine the class and locate the method to be used in handling the event. Therefore the object/POJO contained within the model node should remain consistent throughout the life of the application, or at least whenever the event handler is invoked.
If you want to fix the POJO used by the event handler then prefix the path with a / to make it an absolute node reference. In such a case the POJO is accessed immediately that the event handler declaration is processed and the POJO reference is accessed and retained.
Localization
Generally the field or property name is not appropriate for display as a label or prompt and therefore the PojoPanel looks up the language files for a translation of the property name.
Validation
The input fields can be validated on a field by field basis, or for the POJO as whole. If the #
fieldValidation attribute of the panel is defined then a field validation is added for each input field and the user input is validated when the input loses focus. If the appropriate styles are configured then the background colour for the inputs will reflect the validation status of the fields.
If a validation rule is specified for the panel as a validation element then the POJO is validated as a whole, that is all fields are validated. It is possible to have both styles or validations or one or the other.
The validation rule used for POJOs should map to a validation that implements the PojoValidator interface. The interface provides two methods
POJO Validation interface
/** * Validate the pojo * @param obj * @return the validation result */ public Object validate( Object obj ); /** * Validate a field/property of the pojo * @param obj * @param fieldName * @return the validation result */ public Object validate( Object obj, String fieldName );
The PojoValidator is a basic implementation of this interface. Using this object is possible to wrap third party validation frameworks, as during the validator setup the PojoValidator instance is passed the dataPath attribute, and using the attribute value the validator looks up the POJO upon which it will operate prior to performing the validations.
Spring support
Leveraging the POJO support and runtime form generation enables Aria to used effectively for Spring applications. Spring's remoting technology can expose beans to a remote client and Aria can pick these beans up and create forms and tables for the beans. The PojoPanel described above makes it possible to quickly and easily configure the UI for these beans with little or no coding.
Going one step further it is also possible to provide hints to the panel generation to control various aspects of the UI including styling other attributes of the appearance and behavior. At the core of the support for Spring is the use of reflection and bean inspectors to determine the field types and bean methods. However additional glue logic is provided on the basis that the beans and their attributes are named and handled consistently. Therefore a bean can be bound to UI component and validated against other Spring beans provided that this use of property names is consistent.
With so much provided by Aria creating a Spring based application requires little other than wiring the beans together. The most important requirement in this process is to wire the Spring beans into Aria's data model. Normally an application will have some class of root access point, service facade or some object that ties the various beans together into a session or context of some sort. Aria needs this object to be mapped into the Aria data model, and this can be done as follows:
Setting up Spring support
public class MyPojoRoot extends PojoContext implements PojoValidator { ... /** * Instantiated by the POJO bindings at startup. */ public MyPojoRoot() { try { applicationContext = new ClassPathXmlApplicationContext( new String[] { "spring-config-file-A.xml", "spring-config-file-B.xml" } ); serviceInterface = (SomeBean)applicationContext.getBean( "someBean" ); } catch ( BeansException be ) { be.printStackTrace(); } } public static MyPojoRoot getInstance( Project project ) { MyPojoRoot rootPojo = (MyPojoRoot)project.getObject( "MyPojoRoot" ); if ( rootPojo == null ) rootPojo = new MyPojoRoot(); rootPojo.setProject( project ); return rootPojo; } public Object getRoot() { return getInstance( project ); } public void setProject( Project p ) { project = p; pageMgr = project.getPageManager(); project.setObject( "MyPojoRoot", this ); } public void configure( URL configFileURL ) { rootModel = project.getModel(); } public void modelLoaded() { } /** * Lookup some other bean */ public IOtherBean getOther() { if ( other == null ) other = (IOtherBean)serviceInterface.getOtherBean(); return other; } //-Validation---------------------------------------------------------------- /** * Validate the pojo * @param obj * @return the validation result */ public Object validate( Object obj ) { return serviceInterface.validate( obj ); } /** * Validate a field/property of the pojo * @param obj * @param fieldName * @return the validation result */ public Object validate( Object obj, String fieldName ) { return serviceInterface.validate( obj, fieldName ); } }
The above code also handles the interface to the validation of POJOs.
Once the root of the POJO model has been added it may be convenient to wrap the root POJO with some additional access functions so that various nodes can be more eassily accessed. For example an application often uses the concept of a current node or current object and this may not be provided explicitly by the POJOs. The root node or model nodes hanging of the root might be a good point to wrap the POJOs and add additional functionality. The root node is also a good point to load Spring's configuration files
- Printer-friendly version
- Login or register to post comments