Data binding via connectors

Connectors provide the connection between a data instance of the application and a form instance to load and save the form data. To get data from the application when the form is loaded, the interface ReadConnector must be implemented - a SubmissionConnector for saving. Both interfaces are summarized in DataConnector.

What a "data instance" means depends on the underlying application and must be read or filled by the connector. This could be for example a bean with setter and getter methods or direct access to a database table.

Usage

To use a connector to load the form data, you must call formContext.setConnector(...) before rendering the form and pass the connector instance. So the data is immediately loaded into the FormContext through the connector.

There is also a setConnector() method, which has only a Object as parameter type. In this way you can pass a data object that will be evaluated by the configured Connector class. Therefore the *FormEngine creates an instance of the Connector class passing the data object as constructor parameter. Depending on the configured Connector implementation this can be for example a bean or the ID of a record in a database table.

To load the form data from and save into a class MyData, you can use a connector MyDataConnector. The connector is specified as follows in the form definition:

<connector class="my.example.MyDataConnector" data-class="MyData" />

The indication of a data-class is optional, but speeds up the search for a suitable constructor in the Connector class. There are also the optional attributes read and write. These can be set to false if the connector of a form must not be used to load or save data. When loading the form in a JSP its possible to pass an instance of the class MyData to the FormContext.

DefinitionFactory factory = DefinitionFactory.getInstance();
FormDefinition formDefinition = factory.loadDefinition("/path/to/my/form.xml");
FormContext form = FormContext.createFormContext(request, formDefinition);

MyData myData = ...; // get your own data object

form.setConnector(myData);

In setConnector a connector instance will be generated via reflection, based on the Connector class specified in the form definition. Therefore a constructor is searched that has a parameter of the type of the given data object. Directly in this method then the connector is queried filling up the form components. The thus generated Connector instance is then used while form submission to write the entered data back to the bean.

Without indicating the connector configuration in the form definition, you can create the Connector instance in the JSP itself and pass it directly to the form. Thus, any desired connectors can be used in the same form definition, so the data can be loaded from different sources.

DefinitionFactory factory = DefinitionFactory.getInstance();
FormDefinition formDefinition = factory.loadDefinition("/path/to/my/form.xml");
FormContext form = FormContext.createFormContext(request, formDefinition);

MyData myData = ...; // get your own data object
MyDataConnector dataConnector = new MyDataConnector(myData);

form.setConnector(dataConnector);

Implement a connector

A DataConnector has to provide the methods getValue and setValue. getValue is used by the FormEngine to query the data from the connector to be taken in the form instance. This is done using the form definition, querying all component names one by one. Since a form represents a hierarchical structure, the class HierarchicName is used to specify a component name.

A simple implementation of a connector for the above MyData example, reading the data of a bean can look like this

// must implement DataConnector
// to be used with setConnector
public class MyDataConnector implements DataConnector {

  private MyData bean;

  public MyDataConnector(MyData data) {
    bean = data;
  }

  public Object getValue(HierarchicName cName) {
    String name = cName.toString();

    if ("/fieldA".equals(name)) {
      return bean.getFieldA();
    }
    // else if () ...
  }

  public void setValue(HierarchicName cName, Object value) {
    String name = cName.toString();

    if ("/fieldA".equals(name)) {
      bean.setFieldA(value);
    }
    // else if () ...
  }

  public Object submitValues() {
    // Code to persist th data
  }

  ...
}

In addition, other methods have to be implemented, for example if you have Repeats in the form, methods to query and set the current number of rows. A more detailed interface description is included in the javadocs of the respective classes.

Default implementations

There are some connector implementations that are already used for standard cases or may serve as a basis for your own DataConnector classes.

The AbstractAttributeConnector is intended as a basis for connectors, which determine the data from attribute containers. With it the Attribute names matching the names of the form fields. Field names in groups or repeats will be composed with '/' to the full attribute name together (e.g. street/address). Exemplary implementations are de.imatics.forms.connector.SessionConnector and
de.imatics.forms.connector.RequestParameterConnector.

For beans with suitable setter methods, there is the ReflectionConnector. With it, the setter and getter must be named as the components in the form definition. e.g. for the component "full-name" a bean have the methods getFullName and setFullName. The classes of return values and setter parameters must also match the type of components.

For groups and repeats the setter must deliver complex data objects. For a group "address", which summarizes the address of a person, it must be a method getAddress(), which again returns an Address bean. This should then have e.g. methods like getStreet() and getZip(). In case of repeats lists of beans are expected. Generic type parameter must indicate the type of the contained beans. So with a list of addresses for example following:

public List<AddressBean> getAddresses() {
  ...
}

For an empty Repeat simply return an empty list.
Via setAddress(List<AddressBean> addresses) the list of filled Beans is passed on form submission.

Thus the ReflectionConnector maps the structure of the form to an appropriate Bean structure. Alternatively, the beans can have the simplified methods setValue (String name, Object value) or Object getValue(String name).