Greg has posted 18 posts at DZone. You can read more from them at their website. View Full User Profile

Pivot: A Practical Example, Part 4 - Data Binding

07.23.2008
| 5422 views |
  • submit to reddit
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;
detailChangeLabel.getAttributes().remove(Form.FLAG_ATTRIBUTE);

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

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

if (stockQuote.getChange() < 0) {
detailChangeLabel.getAttributes().put(Form.FLAG_ATTRIBUTE,
new Form.Flag(Alert.Type.ERROR));
}
}
}

StockQuoteView stockQuoteView = new StockQuoteView(stockQuote);
detailRootPane.load(stockQuoteView);
}

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.

Published at DZone with permission of its author, Greg Brown.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Tarun Ramakrish... replied on Wed, 2008/07/23 - 1:13pm

Pivot looks really good. I only recently started reading the set of articles posted by you. Really nice LAF btw.

I am not sure whether I got it right, but data binding still appears to be a bit primitive compared to something like JGoodes databinding - not sure whether you can really call it data binding. It appears to be more manual than automatic.

I not not so much a fan of the XML layout WTKX though its definitely nice. It is my (personal) opinion that XML layouts are good for thrashing demos/prototypes, but finally you just do want to do it in code with a great layout manager like MIGLayout. Ofcourse one could do both - have support for MIGLayout within WTKX. :)

 But it's fantastic! With this and JDK 6 update 10, I think there would be little reason to shift to Flex :)

Greg Brown replied on Wed, 2008/07/23 - 2:47pm in response to: Tarun Ramakrishna Elankath

Hi Tarun, 

Thanks for the feedback. Pivot's data binding mechanism is meant to address two primary use cases: importing data into and exporting data out of a form. Generally, you know when these two things need to happen - when you have retrieved data from the server and when you want to send data to the server. The load() and store() methods correspond directly to these operations. Given this approach, support for automatically responding to changes in form data at other times would have been unnecessary overhead.

Greg

 

Jeff Martin replied on Wed, 2008/07/23 - 5:57pm

Wow - this looks really nice. Does it have a commercial backer?

Greg Brown replied on Wed, 2008/07/23 - 9:11pm in response to: Jeff Martin

VMware was generous enough to fund the initial development of the Pivot platform. However, it is currently being driven by the open-source community. We want Pivot to remain an open platform, but we'd definitely be interested in discussing corporate sponsorship if the opportunity arose.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.