Thursday, September 02, 2010

A new direction for bindings?

I'm happy to report that I've been given company approval to port the relevant components of our Flex data binding library back to Eclipse Data Binding.

I haven't started the actual port yet--there are still some concepts on the Flex side that are not a perfect match to Java and existing idioms in Eclipse Data Binding. You'll see what I mean.

To avoid conflating the port to Java with the general API I'm going to just present what the Flex API looks like.

  Bind.from(source, "foo")
.to(target, "bar");

This binding watches the source.foo property, and writes the new value to target.bar each time a change it detected. Now add some validation and conversion magic:

  Bind.from(source, "foo")
.validate(Validators.stringToNumber)
.convert(Converters.stringToNumber)
.validate(Validators.greaterEqual(0))
.validate(Validators.lessThan(10))
.to(target, "bar");

Here we've added several additional steps in the pipeline.

  • After source.foo changes, we first validate that the string can be converted to a number. If so the pipeline continues to the next step, and terminates otherwise.
  • Next we convert the string to a number
  • Now validate that the number is greater than or equal to zero. If so the pipeline continues to the next step, and terminates otherwise.
  • Now validate that the number is less than 10. If so the pipeline continues and the number, now verified to be in the range [0,10), is written to target.bar.

Now suppose our binding is misbehaving somehow, and we want to troubleshoot. We can add logging steps to the pipeline in between the other steps so we can see exactly what is going on:

  Bind.from(source, "foo")
.log(LogEventLeven.INFO, "source.foo == {0}")
.log(LogEventLeven.INFO, "validate {0} is a number")
.validate(Validators.stringToNumber)
.log(LogEventLeven.INFO, "convert {0} to a number")
.convert(Converters.stringToNumber)
.log(LogEventLeven.INFO, "validate {0} >= 0")
.validate(Validators.greaterEqual(0))
.log(LogEventLeven.INFO, "validate {0} <>
.validate(Validators.lessThan(10))
.log(LogEventLeven.INFO, "set target.bar = {0}")
.to(target, "bar");

(In Flex, string formatting is done with {n} format instead of the %s syntax which Java inherited from C. The log statement passes the values in the pipeline as additional arguments which you can reference in log statements.)

These log steps are a real lifesaver for tracking down and squashing bugs in your binding code.

If you've already worked with Eclipse Data Binding you may have noticed something else: you are no longer constrained to the standard data-binding pipeline. You are free to add steps in the pipeline wherever you like and in any order you like.

Next up is two-way bindings. The bind class provides a twoWay method which connects two bindings to the other one's starting point:

  Bind.twoWay(
Bind.from(source, "foo"),
Bind.from(target, "bar") );

is equivalent to:

  var lock:Lock = new Lock();
Bind.from(source, "foo")
.lock(lock)
.to(target, "bar");
Bind.from(target, "bar")
.lock(lock)
.to(target, "foo");

Notice that each binding has a "lock" step in the pipeline. Only one binding can hold a lock at a time. This solves the common infinite loop problem:

  • source.foo changes. binding one executes, writing the value to target.bar
  • target.bar changes. binding two executes, writing the value to source.foo
  • source.foo changes. binding one executes, writing the value to target.bar
  • ...
  • stack overflow!

Since only one binding can hold the lock at a time, this is what happens instead:

  • source.foo changes. binding one acquires the lock and executes, writing the value to target.bar
  • target.bar changes. binding two attempts to acquire the lock but it is already acquired. binding two aborts.
  • binding one releases the lock

You should never add the same lock more than once to a single binding, since that would guarantee that the binding will never run.

Two-way bindings can use validations, conversions, logging, locks etc just like regular one-way bindings (since two-way bindings are just two one-way bindings wired up to eachother):

  Bind.twoWay(
Bind.from(person, "birthDate")
.convert(Converters.dateToString(dateFormat))
Bind.from(heightText, "text")
.validate(Validators.stringToDate(dateFormat))
.convert(Converters.stringToDate(dateFormat))
.validate(Validators.lessEqual(now))
);

We usually leave out the validations in the model-to-UI bindings. It's usually only important to apply validations when you're copying data back from the UI to the model, to make sure domain constraints are satisfied, such as ensuring that a birth date occurred in the past.

And now for my favorite part: binding from multiple sources, to multiple destinations. Raise your hand if you have ever had to wire up a UI form like this:

  Is there a foo? (o) Yes  ( ) No <-- fooRadioGroup

Enter bar: ____________________ <-- barText

Requirements:

  1. fooRadioGroup.selectedItem is bound to model.foo (a boolean)
  2. barText.text is bound to model.bar (a string)
  3. barText must be enabled iff fooRadioGroup selection is Yes.
  4. When the user clicks "No," set model.bar to null but do not clear the text box. If the user clicks "Yes" again, set model.bar back to the contents of barText

Requirements 1 and 3 are easy:

  var fooLock:Lock = new Lock();
Bind.twoWay(
Bind.from(model, "foo"),
Bind.from(fooRadioGroup, "selectedItem"),
fooLock); // explicitly provide the lock, see more below

Bind.from(fooRadioGroup, "selectedItem")
.to(barText, "enabled");

Requirements 2 and 4 are kind of related to eachother. The model-to-UI binding is simple enough: just write the value straight across:

  var barLock:Lock = new Lock();
Bind.from(model, "bar")
.lock(barLock)
.to(barText, "text");

However the inverse binding (UI-to-model) must also take fooRadioGroup.selectedItem into account to decide whether to write back barText.text (if Yes is selected) or null (if No is selected).

The Bind class has another trick up its sleeve:

  Bind.fromAll(

Bind.from(fooRadioGroup, "selectedItem")
.lock(fooLock),

Bind.from(barText, "text")

)
.lock(barLock)
.convert(function(foo:Boolean, bar:String):String {
return foo ? bar : null;
})
.to(model, "bar");

Look closely. The binding pipelines that we pass to fromAll(...) become the arguments, in the order they are provided, to the converter and validator functions further down the pipeline. The first pipeline is from fooRadioGroup.selectedItem and therefore that boolean value is the first argument to the converter. Likewise, the barText.text pipeline is provided second, so that string value becomes the second argument to the converter.

The converter takes multiple values but returns only a single value. This is where those values get coalesced into a single value that we can write to the model--in this case, a String value or null.

The outer pipeline adds a locking step on barLock, which is expected since we need to prevent infinite loops between the last two pipelines. However we are also locking on fooLock, on the first of the inner pipelines. We had a problem with our bindings overwriting values in the UI depending on the order things were initialized.

It turned out that without that lock, if a new model object was set, then the foo binding would fire first. Thus model.foo was copied to fooRadioGroup.selectedItem. But that would trigger our last binding to execute, so if the new foo value was false, then the last binding would override anything in the text box and set null on the model.bar field, before the model.bar => barText.text binding had a chance to execute!

A good rule of thumb is that any time you need to bind from multiple sources, you should make sure to create a lock to share between all the bindings to relate to the same field in the model.

Obviously there are several concepts that will have to be adapted to work elegantly with our existing APIs. Realms are a missing piece (Flex is single-threaded so we didn't even have to consider it). Also we would want to try to retrofit the existing binding classes to use this new API transparently, like we did with the transition from custom observables to custom properties.

So there you have it. This is my current vision of what Eclipse Data Binding should evolve toward.

Comments?

Wednesday, August 11, 2010

Back in the Saddle

I've been away (and neglecting Eclipse DataBinding) for a long time now. I want to offer my sincere apologies to any and all who've been affected by my lack of attention.

Now that I've settled back into my old routine, I have a new problem. There is a mountain of new / updated bugs in Bugzilla and not enough free time to catch up on all of them. Please help me prioritize by pinging the tasks important to you! Vote, comment, post patches, whatever you have time for--just let me know where the pain points are.

About the time I disappeared from Eclipse, I started a new job at Overstock.com and it's really an awesome place to work. We're always looking to hire new programmers, so send me a line if you're looking for something new.

While at Overstock I've been doing some Flex development, and (surprise!) working on a data binding library in Flex. Yes, technically Flex already has declarative data binding baked in. What I'm working on brings data binding to the ActionScript side, and gives you full support over the binding pipeline: conversions, validations (sound familiar?), synchronized access, one- and two-way bindings, bindings from multiple sources and coalescing the values together. It's really cool.

This work in Flex has been a golden opportunity to make a fresh start and use the lessons learned from Eclipse DataBinding. Pending company approval I hope to port all that goodness back to Java sometime soon. Stay tuned.

Disclaimer: Opinions are my own and do not reflect or represent my employer.

Thursday, February 12, 2009

Bind a viewer in one statement with ViewerSupport

We've added the ViewerSupport class to simplify setting up viewers (including tree viewers), usually in just one statement.

Before:

ObservableListContentProvider contentProvider =
new ObservableListContentProvider();
viewer.setContentProvider( contentProvider );
viewer.setLabelProvider(
new ObservableMapLabelProvider(
BeansObservables.observeMaps(
contentProvider.getKnownElements(),
new String[] {
"title", "releaseDate", "director", "writer" } ) ) );
viewer.setInput(BeansObservables.observeList(model, "movies"));


After:

ViewerSupport.bind(
viewer,
BeansObservables.observeList(model, "movies"),
BeanProperties.values(new String[] {
"title", "releaseDate", "director", "writer" } ) );

Master-detail editing with multiple selection

A few weeks ago we released DuplexingObservableValue. This class, among other things, helps make possible master-detail editing on a multiple selection:


Because the selected movies have the same director and same writer, those values are displayed in the master-detail fields at the bottom. However, since they have different titles and release dates, those fields use a stand-in "multi value" instead.

Editing the release date field simultaneously changes the release date of all movies in the selection.


Here's the code that was used to hook up the detail fields to the multi-selection of the viewer:

IObservableList selections = ViewerProperties.multipleSelection()
.observe(viewer);

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(title),
DuplexingObservableValue.withDefaults(
BeanProperties.value("title").observeDetail(selections),
"", "<Multiple titles>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(releaseDate),
DuplexingObservableValue.withDefaults(
BeanProperties.value("releaseDate").observeDetail(selections),
"", "<Multiple dates>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(director),
DuplexingObservableValue.withDefaults(
BeanProperties.value("director").observeDetail(selections),
"", "<Multiple directors>"));

dbc.bindValue(
WidgetProperties.text(SWT.Modify).observe(writer),
DuplexingObservableValue.withDefaults(
BeanProperties.value("writer").observeDetail(selections),
"", "<Multiple writers>"));
See the full code listing for this snippet here.

Disclaimer: The method DuplexingObservableValue.withDefaults() was added after the 3.5M5 milestone, so in order to use it you need to check out the databinding projects from CVS HEAD, or be using a more recent integration build.

Tuesday, February 03, 2009

Introducing the Properties API

Observables are hard to implement. There are a handful of rules, guidelines and corner cases that observables implementors must pay attention to for data binding to work properly. Observables must call ObservableTracker.getterCalled() for things like ComputedValue or MultiValidator to work; observables must ensure that all invocations are made from within the realm; a ChangeEvent must be fired before ValueChangeEvent; the list goes on. To an extent we've been able to isolate these concerns within the framework, but over time I couldn't help noticing that many of our observables followed a template--a lengthy, complicated one.

In June 2008 I stumbled across bug 194734 and I thought Tom was on to something. I started working on a prototype, which we've been evolving and refining for the past six months. I'm very pleased with the result, and if you've tried implementing your own observables before, I think you will be too.

There are four types of properties: IValueProperty, IListProperty, ISetProperty and IMapProperty. Properties serve two main purposes:
  • They act as convenient, portable observable factories for a particular property.
  • They act as strategy objects for the observables they create. That is, the observables use the properties to access, modify, and listen to the property source object.
Suppose you want to observe the "name" property of a bean object:

Person person = new Person("Joe");
IValueProperty nameProperty = BeanProperties.value("name");
IObservableValue personName =
nameProperty.observe(person);

One of the strengths of properties is how easy it is to combine them to observe nested attributes:

IListProperty childrenProperty = BeanProperties.list("children");
IValueProperty nameProperty = BeanProperties.value("name");
IObservableList namesOfChildren =
childrenProperty.values(nameProperty).observe(person);

or, a shorter version:

IObservableList namesOfChildren = BeanProperties
.list("children").values("name").observe(person);

Properties are reusable:

IValueProperty selection = WidgetProperties.selection();
IObservableValue buttonSelection = selection.observe(button);
IObservableValue comboSelection = selection.observe(combo);
IObservableValue spinnerSelection = selection.observe(spinner);

But most importantly, they are easy to implement. Let's close with a real-world example:

DataBinding does not come with built-in support for observing the selection of a DateTime widget (see bug 169876 for an explanation why). However a useful use case is to observe the date portion (year+month+day) and set the time of day to midnight.

The following is a complete property implementation for the DateTime#selection property, that you are free to use in your own projects:

public class DateTimeSelectionProperty extends SimpleValueProperty {
public Object getValueType() {
return Date.class;
}

// One calendar per thread to preserve thread-safety
private static final ThreadLocal calendar =
new ThreadLocal() {
protected Object initialValue() {
return Calendar.getInstance();
}
};

protected Object doGetValue(Object source) {
Calendar cal = (Calendar) calendar.get();

DateTime dateTime = (DateTime) source;
cal.clear();
cal.set(dateTime.getYear(), dateTime.getMonth(),
dateTime.getDay());
return cal.getTime();
}

protected void doSetValue(Object source, Object value) {
Calendar cal = (Calendar) calendar.get();
cal.setTime((Date) value);

DateTime dateTime = (DateTime) source;
dateTime.setYear(cal.get(Calendar.YEAR));
dateTime.setMonth(cal.get(Calendar.MONTH));
dateTime.setDay(cal.get(Calendar.DAY_OF_MONTH));
}

public INativePropertyListener adaptListener(
ISimplePropertyListener listener) {
return new WidgetListener(listener);
}

private class WidgetListener implements INativePropertyListener,
Listener {
private final ISimplePropertyListener listener;

protected WidgetListener(ISimplePropertyListener listener) {
this.listener = listener;
}

public void handleEvent(Event event) {
listener.handlePropertyChange(
new SimplePropertyEvent(event.widget,
DateTimeSelectionProperty.this, null));
}
}

protected void doAddListener(Object source,
INativePropertyListener listener) {
((DateTime) source).addListener(
SWT.Selection, (Listener) listener);
}

protected void doRemoveListener(Object source,
INativePropertyListener listener) {
((DateTime) source).removeListener(
SWT.Selection, (Listener) listener);
}

public IObservableValue observe(Object source) {
// For widgets, we want the display realm instead of
// Realm.getDefault()
Realm realm = SWTObservables.getRealm(
((Widget) source).getDisplay());
return observe(realm, source);
}
}


Now we can observe the selection of a DateTime:

IObservableValue selection =
new DateTimeSelectionProperty().observe(dateTime);


Today we released WidgetValueProperty as public API, but did not make it in time for M5. However in the future (starting in the next integration build), implementing properties for widgets will be much simpler:

  • Omit the adaptListener, doAddListener, and doRemoveListener methods
  • Omit the WidgetListener class
  • Omit the observe() method
  • Add a no-arg constructor with a call to super(eventType) in the constructor.

In the example the constructor would call "super(SWT.Selection)". As long as the the widget supports untyped listeners, WidgetValueProperty will handle the listener parts for you.

Friday, January 30, 2009

Watch this guy: Angelo Zerr

A couple of months ago, Angelo Zerr contacted me out of the blue and asked for feedback on a project he had worked on called "DOM Binding". I was pretty busy at that time and put it on my "someday maybe" list, but he kept pinging me about it and even contributed his code in a Bugzilla so I decided to take a look. Even then, I thought "yeah, w3c DOM stuff, what could be more boring".

Until I ran the small demo app he had written.

I was excited and impressed by this demo app and with how little code it implemented bi-directional synchronization between a forms-based UI and data in XML form. Quite a mouthful, I know, but imagine you had to write one of the editors provided by PDE: some random XML file format cooked up by someone else, which is to be presented in a nice form-based UI through which mere mortals can edit the data. At the same time, the editor should allow experts to view and change the XML text itself, such that any changes get reflected in the UI.

I had always assumed that implementing this would be a major pain in the neck - you would probably write a bunch of Java classes that would be the in-memory representation of the XML data, call the verbose w3c DOM API to get the data into your Java objects, write UI code for editing the Java objects, and sprinkle in code that keeps everything in sync. Lots of repetitive code, with many opportunities to make small mistakes that would be hard to find or debug.

Enter Angelo - he took the Eclipse data binding framework and implemented observables that work against w3c DOM nodes directly, resulting in suprisingly concise code. This is one of those things that you have to see in action to "get" it, an ideal topic for a screencast.

Last night, I decided to learn how to make screencasts by using this as a warm-up exercise. The result turned out pretty amateurish. I apologize for all the "erm"s and that you can hear me breathe, but I think it's still worth watching. Click to watch the screencast (five minutes, 7 MB).

Btw - Angelo, congratulations on becoming an e4 committer!

Wednesday, February 27, 2008

UI programming BoF at EclipseCon?

Better late than never, I submitted a BoF proposal today. Comments and suggestions welcome:

UI Component Programming Best Practices

This BoF is about sharing best practices for programming of user interface components. With "component" we mean a piece of code that implements the inner parts of dialogs, wizard pages, views, or editors.

How do we program user interface components today? How could it be made easier, less error-prone, and less complex? Topics discussed in this BoF may include SWT, Nebula, JFace, declarative UIs, data binding, and techniques for ensuring testability of UI components.

Programming of user interfaces at the level of RCP or the Workbench (where you assemble UI components into plug-ins and applications) are out of scope for this BoF.