-
Notifications
You must be signed in to change notification settings - Fork 17
Bean binding
The Handling user input section covered methods for manually pushing data into the component model using the generic
setData
method from the DataBound API or component-specific methods such as setText
. When using the DataBound API
there is no automated way to map WComponents to a complex data type instance variable and application actions need to
repeatedly call setData
/getData
on every component which needs to display data.
To reduce the amount of data-binding code which developers need to write a "Bean-aware" API also is available on all input components and certain display components. Bean-aware components allow developers to specify which field a component is interested in and have the component call the bean's accessor methods as needed.
In the same manner in which DataBound components have fine-grained data associated with them, bean bound components can
have a complex data type bound directly to them by calling the setBean
method. The data is bound directly to the
component and will be stored in the user session for guaranteed access times.
Here is an example code snippet showing the DataBound method of providing data:
...
WText firstNameField = new WText();
WText lastNameField = new WText();
...
...
// And in each action
public void execute(ActionEvent event) {
// ...
// the setData and setText methods are equivalent
firstNameField.setData(selectedPerson.getFirstName());
lastNameField.setData(selectedPerson.getLastName());
// etc. (repeat for each field)
}
The bean-bound equivalent is:
...
WText firstNameField = new WText();
WText lastNameField = new WText();
firstNameField.setBeanProperty("firstName");
lastNameField.setBeanProperty("lastName");
...
...
// And in each action
public void execute(ActionEvent event) {
// ...
firstNameField.setBean(selectedPerson);
lastNameField.setBean(selectedPerson);
// etc. (repeat for each field)
}
The bean property tells the component which piece of data inside the bean it needs to use. In the example setting the
bean property to "firstName" will cause the text component to call the getFirstName()
method on the bean whenever it
needs to obtain the value. The bean property uses the Apache Commons BeanUtils property syntax with an extension of "."
to signify that the entire bean should be used.
At first glance, this doesn't seem to provide much of a gain as each action is still required to update each field in the UI. The default implementation of a bean-aware component (WBeanComponent) will, however, search for the bean in its parent hierarchy if a bean has not been directly bound. This approach lets developers bind complex data types to different sections of the UI.
The action code for the previous example can now become much simpler:
// ...
WText firstNameField = new WText();
WText lastNameField = new WText();
firstNameField.setBeanProperty("firstName");
lastNameField.setBeanProperty("lastName");
// ...
public void execute(ActionEvent event) {
// ...
// somePanel extends WBeanComponent and contains all fields
somePanel.setBean(selectedPerson);
}
Directly binding a bean to a component can result in excessive memory consumption in the underlying Portlet/Servlet user session. Applications can indirectly bind beans to components using a bean-provider to reduce memory consumption. When a bean provider is used at a high level in the UI the amount of data stored in the session is significantly less than when using the Bean- or Data-Bound APIs as the low-level components do not need to store any user-specific data.
The bean-provider interface is quite simple as there is only one method which needs to be implemented:
getBean(BeanProviderBound)
. The method takes in the component that is bound to the provider and returns the bean. The
BeanProviderBound interface can be used to obtain the bean's id. The provider and bean Id can be set at any level in the
WComponents tree. The example below shows the implementation of a BeanProvider which calls a fictional
"MyApplicationService" to retrieve a bean based on its id.
// ...
WText firstNameField = new WText();
WText lastNameField = new WText();
firstNameField.setBeanProperty("firstName");
lastNameField.setBeanProperty("lastName");
// ...
somePanel.setBeanProvider(new BeanProvider() {
public Object getBean(BeanProviderBound beanProviderBound) {
String beanId = beanProviderBound.getBeanId();
return MyApplicationService.getSomething(beanId);
}
});
...
public void execute(ActionEvent event) {
// ...
somePanel.setBeanId("<some meaningful id>");
}
A typical bean provider might read a bean from a cached service. If the data expires from the cache it will have to be re-read from the underlying service. For this reason it is up to each individual application to determine whether to bind to a bean directly or indirectly. Note that bean values are temporarily cached in the user session per action/render phase to avoid excessive calls to the provider.
AbstractInput, the superclass for data-entry fields, is a bean-aware component. An input component can therefore have many sources of data at any given moment. If there are multiple sources of data they are returned in the following order:
- user entered data (where it differs from the input field's default/bean value);
- a directly bound bean (explicitly set using
setBean
); - an indirectly bound bean as provided by a BeanProvider.;
- a bean provided by a bean-aware parent component;
- the field's default value.
Input fields do not update data in the bean by default as it may be transient data sourced from a bean provider or
data which must not be updated until the user chooses to commit the changes. Applications can update a bean's data
manually by calling the utility method bordertech.wcomponents.WebUtilities.updateBeanValue(WComponent)
on a section
of the application tree.