Malcolm Edgar, Apache Click Vice President, recently had a chat with SD Times editor in chief, David Rubinstein. Great to see Click getting some much deserved exposure.
Full interview can be viewed here.
Saturday, July 24, 2010
SD Times interview with Apache Click vice president
Wednesday, July 14, 2010
Apache Click 2.2.0: DataProvider
In this three part series I'll blog about some of the new features added in 2.2.0, namely:
- DataProviders (Japanese version)
- Explicit binding (Japanese version)
- Dynamic Form Validation ( Japanese version)
To understand the benefits of DataProviders, we need to look at the problem they are trying to solve. To start off with let's do a quick recap of the major life cycle events of a Click Page. Below we list the events in the order they are executed:
- <<init>>: page is created
- onInit: page controls are created and added to the page
- onProcess: control values are bound to request parameters, control values are validated and action listeners are fired
- onRender: database intensive operations are performed here
For example:
public class CustomerPage extends BorderPage { private Table table = new Table("table"); private ActionLink editLink = new EditLink("edit"); @Override public void onInit() { super.onInit(); table.add(new Column("firstname")); ... editLink.setActionListener(new ActionListener() { public boolean onAction(Control source) { Map params = Collections.singletonMap("id", editLink.getValue()); // Redirect to edit customer page and pass the selected customer ID setRedirect(EditCustomerPage.class, params); return false; } }); } @Override public void onRender() { // Database intensive operation: retrieving all customers from the database List<Customer> customers = getCustomerService.getCustomers(); table.setRows(customers); } }In the example above you can see that we don't want to hit the database and retrieve all the customers only to be redirected to another page that does not render the customers we retrieved. In other words, because the customers won't be rendered by the page we redirect to, we don't want to pay for the database hit. Since the onRender event is skipped if an action listener returns false, it makes sense to place database intensive code there.
Inconsistent
Unfortunately this pattern cannot be applied to all controls. Some controls need their values populated before the onProcess event, either for validation or parameter binding purposes. For example, the Select control validation depends on its values to determines whether or not a valid selection was made. FormTable needs to have it's rows set in order to update its entity values against incoming request parameters.
Having some of the controls populated in onInit and others in onRender is inconsistent and a common pitfall for new users as they have to figure out which controls should be populated in which event.
DataProvider
The solution used in Click was to add a DataProvider interface to enable on demand data loading. A DataProvider has a single method, "public List getData()", that the control can invoke when it needs it's data. For example, a Table will invoke getData when it is rendered, a Select will invoke getData when it is validated and a FormTable will invoke getData when it is processed.
DataProviders leads to a more consistent Page implementation where all control creation logic can be encapsulated in the onInit event or even the page constructor. For example:
public class CustomerPage extends BorderPage { private Table table = new Table("table"); private Select select = new Select("markets"); @Override public void onInit() { super.onInit(); table.add(new Column("firstname")); ... table.setDataProvider(new DataProvider() { public List<Customer> getData() { return getCustomerService().getCustomers(); } }); select.setDataProvider(new DataProvider() { public List<Option> getData() { List options = new ArrayList(); for (Market market : getMarketService().getMarkets()) { options.add(new Option(market.getId(), market.getName()); } return options; } }); } }In the example above we use DataProviders for both the Table and Select control. The Table data is only retrieved when the Table is rendered, so in the event of a redirect, the database operation is skipped. The Page is now consistent as all control setup logic is placed inside the onInit event.
Is onRender still necessary?
Absolutely. While DataProviders allow control creation logic to be encapsulated within the onInit event, not all data needs to be represented with controls. The Page template (Velocity) for example can be used to render markup for the customers passed from the page. onRender could still be used to retrieve the customers and make it available to the template. For example:
public class CustomerPage extends BorderPage { public void onRender() { addModel("customers", getCustomerService().getCustomers()); } }
<table> ... #foreach($customer in $customers) <tr> <td> $customer.name <td> <td> $customer.holdings <td> </tr> #end </table>
If an action listener returns false, onRender is skipped and the database operation won't be performed, which is exactly the behavior we want.
Hope this overview was helpful. In the next installment I'll cover Explicit binding and Dynamic Form Validation, which both simplifies dynamic form creation.
Labels:
Click 2.2.0,
features
Subscribe to:
Posts (Atom)