Over a million developers have joined DZone.
Platinum Partner

Pivot: A Practical Example, Part 4 - Data Binding

· Java Zone

The Java Zone is brought to you in partnership with ZeroTurnaround. Discover how you can skip the build and redeploy process by using JRebel by ZeroTurnaround.

This is the fourth in a series of five articles that walk through the implementation of a simple but practical Pivot application called Stock Tracker. The previous article discussed "web queries", Pivot's native means of communicating with remote data services. This section focuses on data binding. The final article will cover Pivot's support for localization. -- Greg Brown

Data binding is the process of automatically mapping values between a set of user interface elements and an internal data representation; for example, from a order entry form to a collection of database fields or vice versa. Data binding can help simplify development by eliminating some or all of the tedious boilerplate code that often goes along with this type of programming.

In Pivot, data binding is controlled by the load() and store() methods of the Component class:

public void load(Dictionary<String, Object> context) {...}
public void store(Dictionary<String, Object> context) {...}

The Dictionary argument passed to these methods provides the "bind context": a collection of name/value pairs representing the data to which the components are bound. Each bindable component can be assigned a "bind key" that associates the component with a value in the context. The default implementations do nothing; components that support data binding override them to import data to the component from the context during a load, and export data from the component to the context during a store.

Data binding is most often supported by components that accept and present user input (such as a text field), but it can also be implemented by read-only components such as labels and progress meters. Most components allow a caller to bind only to a single value (such as the "text" property of a label), though some support additional bindings (for example, a checked list view that allows a caller to bind to both its items' checked and selected states).

It is important to note that it is not possible to bind to a container directly. However, containers may act as nested bind contexts - when a bind key is assigned to a container, it is assumed to point to a nested Dictionary instance representing a subordinate bind context. This enables complex JSON object graphs returned from a web query to be seamlessly mapped to a set of data-bound components arranged in a non-trivial layout, for example.

Data Binding in the Stock Tracker Demo

The Stock Tracker demo isn't quite that sophisticated. It uses a single, flat bind context to populate the fields in the quote detail view. The bind context is actually the row data retrieved from the web query for the selected stock. This is why we requested more data than we seemed to need from the GET query: the extra fields are used to fill in the data in the detail form.

The bound components, in this case, are read-only labels - Stock Tracker uses a one-way binding to map the retrieved quote data to the text property of each. We specified the name of the key to use for each label in the stocktracker.detail.wtkx file:

<Label label="%value" textKey="value"/>

The actual binding occurs when the selection changes in the table view; as we saw in the event handling section, the selection change handler calls the refreshDetail() method in response to a selection change event. The code for this method is as follows:

private void refreshDetail() {
int firstSelectedIndex = stocksTableView.getFirstSelectedIndex();
removeSymbolsButton.setEnabled(firstSelectedIndex != -1);

StockQuote stockQuote = null;

if (firstSelectedIndex != -1) {
int lastSelectedIndex = stocksTableView.getLastSelectedIndex();

if (firstSelectedIndex == lastSelectedIndex) {
List tableData = (List)stocksTableView.getTableData();
stockQuote = tableData.get(firstSelectedIndex);

if (stockQuote.getChange() < 0) {
new Form.Flag(Alert.Type.ERROR));

StockQuoteView stockQuoteView = new StockQuoteView(stockQuote);

The method does the following:

  • Obtains the first selected index in the table (if more than one item is selected, we don't want to show anything in the detail)

  • Clears the "flag" attribute from the detail change label (the Form container allows a caller to tag fields with a flag value, for use in data validation or just simple notification - here, a flag is used to indicate a negative change in the stock value)

  • Gets a reference to the table view's data model and then to the data for the selected row

  • If the change percentage is negative, shows a red "error" flag next to the detail label

  • Wraps the row data in an instance of StockQuoteView and calls load(), populating the form with data from the selected quote

StockQuoteView is a "decorator" - it implements the same Dictionary interface as the actual row data, but, like the cell renderers used by the table view, it ensures that the data is formatted and presented in a readable manner in the detail view.

Note that the load() method is actually called on the parent container of the Form, rather than on the form itself. This is because we also want to bind to the label that contains the company name, which is not a child of the Form. A nested container does not automatically imply the existence of a sub-context - sub-contexts are created only when a nested container is assigned its own bind key. Because a bind key is not defined for it, the form simply inherits the bind context that was passed to its parent.

The Java Zone is brought to you in partnership with ZeroTurnaround. Discover how you can skip the build and redeploy process by using JRebel by ZeroTurnaround.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}