Skip to main content

Update on Beans Binding (JSR 295)

Posted by zixle on February 2, 2007 at 11:22 AM PST

Way back in May (YOW!) of last year I blogged on beans binding (JSR 295). While not much progress has been made externally, a ton of progress has been made before the expert group and internally. For the anxious folks, here's a small demo showing off various features of beans binding (I got lazy, the demo requires 1.6; for the folks still on 1.5, sorry):

Try selecting multiple elements in the table, notice the label beneath the table updates to reflect how many elements are selected. Similarly notice that when multiple rows are selected changing one of the detail values (such as the slider) updates all. But I'm getting ahead of myself.

WARNING: beans binding is in no way done and will most likely change from what I'm going to detail here.

Quick refresher: beans binding is all about binding two properties of two objects together. Properties in this sense refers to the beans notions of properties, the "foo" property is identified because you have setFoo/getFoo. If the two objects are observable (support PropertyChangeListeners), then beans binding can track changes and keep the two sides in sync. Don't get hung on using "bean" here, nearly any Object is a bean and if you're following common design patterns, you're in good shape.

Before getting into how the UI is configured, let me describe the model for this app. The model consists of a List, where Bug is a trivial bean with properties for id, priority, synopsis ... And of course Bug is observable, meaning it notifies listeners of changes by way of PropertyChangeListener.

For this example I'm going to use BindingContext. BindingContext isn't strictly necessary; it's useful for managing a set of bindings. I envision a BindingContext per form:

context = new BindingContext();

We want to display the List in the table, where each row corresponds to a Bug, and each column corresponds to a property of the Bug. Here's how this is done with beans binding:

List<Bug> bugs = ...;
Binding tableBinding = context.addBinding(
    bugs,      // Source for the binding, the List of bugs in this case.
    null,      // Expression, relative to the source, used in obtaining the property.
               // For this example it's null, meaning use bugs as is.
    bugTable,  // Target of the binding, a JTable in this case.
    "elements"); // The property of the target to bind to.

By itself, this binding specifies the rows of the table. The astute reader will wonder just what "elements" is? JTable has no "elements" property, right? Turns out when you're binding using an object based binding approach there are a number of properties you would really like to have, but just aren't there. Beans binding provides a standard way to extend objects, adding properties for the sake of binding. "elements" is such a property, and equates to the rows of the table (as Objects). Under the covers this is building a private TableModel implementation, backed by the elements in the List. For the demo, the List is a plain old ArrayList. If the List were observable (I'm not going to get into details of what an ObservableList is here), then JTable would track changes made to the underlying list.

While this example isn't showing it, you can also specify a converter, validator and a handful of other properties per binding.

The next step is to specify how the values for each column are obtained:

tableBinding.addBinding(
    "${ID}",    // Expression evaluated relative to each Bug.
                // In this case, it's treated as bug.getID().
    null,       // Target value (I'm not going to get into this parameter now)
    TableColumnParameter, 0); // Specifies the binding applies to the first column

This specifies the expression ${ID} should be used to to obtain the value for the entries in the first column. This expression is evaluated relative to the element of each row (a Bug in this example). The expression ${ID} maps to invoking bug.getID() for each row. Of course the appropriate listeners are installed to keep everything in sync, meaning that if someone invokes aBug.setID(xxx), the private TableModel implementation built underneath will fire the appropriate notification and JTable will update the display. Similarly, if you edit the table, binding will end up invoking aBug.setID(xxx). Nice!

The expression language for the source is nearly the same one used by JSF/JSP. The only changes are to support a handful of things needed for binding, none-the-less it's nearly the same. Do a search for JSF expression language if you're interested in the specifics. The language for the target is just a dot separated property syntax. I'm not going to get into why these are different now.

The second and third columns are similarly bound:

tableBinding.addBinding("${priority}", null, TableColumnParameter, 1);
tableBinding.addBinding("${synopsis}", null, TableColumnParameter, 2);

Similar to the "elements" property, JTable also exposes two properties that correspond to the selection: "selectedElement" and "selectedElements". The "selectedElement" property corresponds to selected object from the List bound to the "elements" property. The "selectedElements" contains the List of selected elements, again from the List bound to the "elements" property.

Each of the detail components (text fields, slider, combobox) are driven from the selection of the table. As such, they are bound to a property of the "selectedElements" property from the table. This means that any time the selection of the table changes, the text field is updated appropriately. The following shows the code for the first text field:

Binding textFieldBinding = context.addBinding(
    bugTable,                 // Source of the binding, the JTable in this case.
    "${selectedElements.ID}", // Expression relative to the source. Evaluates to the
                              // the id property of each of the selected elements
    idTF,                     // Target of the binding, a JTextField here.
    "text",                   // The target property to bind to.
    // The next line specifies the 'text' property should change as you type.
    // The default is to change the property on enter/focus leaving.
    TextChangeStrategyParameter, TextChangeStrategy.CHANGE_ON_TYPE);

Notice the text field is bound to the "selectedElements.ID" property. But "selectedElements" is a List and has no "ID" property! How can this be? In the modified version of EL being used any properties relative to a List are expanded in place, and the result of the evaluation is a List of values. For example, if there are two selected elements, the modifier version of EL evaluates this expression to the "ID" for each of the elements. Beans Binding then passes the List of values through a ListCondenser that returns one value to pass to the target of the binding. For this example I wanted to show all the selected values wrapped in quotes and separated by commas. Here's the code that does this:
textFieldBinding.setListCondenser(ListCondenser.concatenatingCondenser(
    """,   // The string placed before each element
    """,   // The string paced after each element
    ", ")); // The string that separates each element.

The binding I want to mention is the summary label. Notice the summary label shows a count for the selection. This can be handled with a function in EL. Here's the code:
context.addBinding(
  bugTable,  // The source of the binding, the table in this case.
  // The expression evaluated relative to the source. Notice this makes use
  // of the function "listSize", that returns an size of the list supplied to it.
  "${bb:listSize(selectedElements)} of ${bb:listSize(elements)} are selected",
  summaryLabel, // The target of the binding, a JLabel here.
  "text");      // The target property to bind to

This shows how to use static methods as part of the EL expression. The expression "${bb:listSize(selectedElements)}" evaluates to the size of the list. As the "selectedElements" property of the table updates every time the selection changes, the summary label is kept in sync appropriately.

The last step is to realize the bindings, or install all the necessary listeners so that everything remains in sync:

context.bind();

And that's it.

There's a lot that I haven't gotten into, but this should give you a feel for where beans binding is headed.

Next question I know is going to be asked: where's the source? I'm working on it!

    -Scott