Bindings in general

This document is a specification draft for Tweed 0.2.0. Current version does not implement the same level of features.

A Binding is the set of operations required to update a View from a Model, or a Model from a View. A Model is an object containing parameters required for server-side processings. A View is a graphical object that is a part of the GUI. Most of time, bindings mean a 1-1 relationship between Model's properties and View's widgets.

Bindings are applied just before / after invoking a Command on the server:

  1. View to Model : before sending a Command on the server. Some Model objects are created, and filled with Views' values. A Command takes Model objects as input parameters.
  2. Model to View : Just after a Command returned. The Command has been updated, now containing Model objects as output parameters.

Every time bindings are applied, incorrect user input may be detected. The user receives a description of the validation failure, which should include, as possible, a reference to the Widget(s) containing incorrect / inconsistent values.

Creating a View

Let's define a View object. It is named MyView and has three widgets which allow data input. Those three widgets will be involved in Bindings.

  public class MyView1 extends ... {
    /*package*/ widget1 = new JTextField() ;
    /*package*/ widget2 = new JTextField() ;
    /*package*/ widget3 = new JSpecialWidget() ;
  }

Please note that all widgets involved in Bindings have a package-private scope.

Creating a Model

Now let's define a Model object. It is named Model1 .

  public interface MyModel1 extends Model {
    int getValue1() ;
    void setValue1( int value1 ) ;
    String getValue2() ;
    void setValue2( String value2 ) ;
    Date getValue3() ;
    void setValue3( Date value3 ) ;
  }

A Model object is an interface, which extends tweed.binding.Model . It defines properties with getters and setters, nearly as defined in the JavaBeans specification. It is possible to take advantage of some IDEs to generate getters / setters easily.

Creating an automatic Binding

Let's define a Binding between a Model1 and a View1 instance as follows:

  • Value1 <--> widget1
  • Value2 <--> widget2

This basic case is very simple to implement. We just need to:

  • Create a new class derived from tweed.binding.Binding .
  • Provide a constructor taking a tweed.context.Context object as parameter.
  • Implement a define() method defining unconditional bindings between Model properties and View widgets.

  public class MyBinding0 extends Binding {

    private final Model1 myModel ;
    private final View1 myView ;

    public class MyBinding1( Context context, Model1 myModel, View1 myView ) {
      super( context ) ;
      this.myModel = myModel ;
      this.myView = myView ;
    }

    protected void define() {
      attach( myModel.getValue1(), myView.widget1 ) ;
      attach( myModel.getValue2(), myView.widget2 ) ;
    }

  }

This minimal Binding will work, because Tweed knows how to deal with Model1 properties' types (which are int for Value1 and String for Value2 ). Recognizing property types will help validating user input. In the present case, default validation failure descriptions will be provided.

If we want more accurate validation failure messages, we just have to add a parameter to the attach() method call like this:

    attach(
        myModel.getValue1(),
        myView.widget1,
        "Something wrong with widget 1"
    ) ;

Creating a custom Binding

Now we can imagine situations where automatic Bindings are not enought. For example, MyView1 's widget3 is a JSpecialWidget , a class that Tweed doesn't know how to deal with. It is possible to define custom bindings simply by overriding modelToView or viewToModel methods.

Let's redefine viewToModel() :

  public void viewToModel() {
    super() ; // applies automatic bindings
    if( myView.widget3.isValid() ) {
      myModel.setProperty3( myView.widget3.getValue() ) ;
    } else {
      createMessage( "Widget 3 contains something invalid" ).
          addComponent( myView.widget3 ) ;
    }
  }

In the example above, we know that user input was wrong with myView.widget3.isValid() , but we could also catch an exception. The Binding class provides a createMessage method which creates a message with a reference to involved components. It is possible to add several components by chaining addComponent calls like this:

createMessage( "..." ).addComponent( c1 ).addComponent( c2 ) .

Now let's redefine modelToView() :

  public void modelToView() {
    super() ; // applies automatic bindings
    try {
      myView.widget3.setValue( myModel.getProperty3() ) ;
    } catch( ValidationException ex ) {
      createMessage( "Something wrong with widget 3" ).
          addComponent( myView.widget3 ) ;
    }
  }

In the example above, we catch a ValidationException (a descendant of java.lang.RuntimeException thrown when accessing a Model's property on which a validation error was detected server-side. Instead of letting the Binding abort current method execution, we provide a validation failure message of our own.

Creating hierarchical Bindings

Bindings can be reused and chained hierachically. Let's imagine that MyModel1 and MyView1 are aggregated in MyModel0 and MyView0 . With MyModel0 and MyView0 looking like this:

  public class MyView0 extends ... {
    // Some other widgets here...
    /*package*/ nestedView = new MyView1() ;
  }

  public interface MyModel0 extends Model {
    // Some other properties here...
    MyModel1 getMyModel1() ;
    void setMyModel1( MyModel1 myModel1 ) ;
  }

We can reuse the Binding1 class in Binding0 implementation:

  public class MyBinding0 extends Binding {

    private final Model0 model0 ;
    private final View0 view0 ;

    public class MyBinding0( Context context, Model0 model0, View0 view0 ) {
      super( context ) ;
      this.model0 = model0 ;
      this.view0 = view0 ;
    }

    protected void define() {
      // Some other attachments here...
      new MyBinding1(
          this,
          model0.getMyModel1(),
          view0.nestedView
      ) ;
    }

  }

Passing the instance of MyBinding0 as parameter to MyBinding1 constructor provides enough information. Now we need to add another constructor to MyBinding1 :

    public MyBinding1( Binding parent, Model1 myModel, View1 myView ) {
        super( parent ) ;
        this.myModel = myModel ;
        this.myView = myView ;
    }

Instantiating a Model

Only the interface defining Model's property needs to be specified. The implementation is built "magically" by a Factory, and acts like a "stupid" bean just able to keep values.

  MyModel1 model = ( MyModel1 )
    getContext().getModelFactory().create( MyModel1.class ) ;

This requires to have access to a valid tweed.context.Context instance, which is always the case when implementing Actions or Commands.

Invalidating Model's properties

Server-side Command implementations perform validations on Models. When one or more Model's properties are involved in a validation failure, they can be tagged as "dirty".

  makeModelDirty( "The message" ).
      addProperty( myModel.getProperty1() ).
      addProperty( myModel.getProperty2() )
  ;

The code above is supposed to appear in a concrete Command implementation. The tweed.invocation.Command class defines the createTag method, which works very like tweed.binding.Binding.createMessage .

Implementation notes

It seems that attach( myModel.getValue1(), ... ) takes the value returned by myModel.getValue1() as input parameter. But remember: as we're defining the Binding, we don't want to access to the value, we want to know the accessor itself (the method). Since Java doesn't support expressions like: myModel.Value1 where Value1 is the very property itself, we found a workaround.

All Models are defined as interfaces, it is possible to pass a Dynamic Proxy of them (see java.lang.reflect.Proxy ), and use some flags to detect which method of the Model is being called from inside the attach method. This is enough to know how to access to Model's property when the Binding is be applied.

Another solution would be to define bindings in some place the compiler doesn't know about. But this would make us loose the benefit of a compile-time validation.