Document Information

Last modified:
2009/09/25 20:07 by axelhinrichs

Data Binding

This is preliminary documentation of a feature that is under active development in SVN trunk. Previews were made available in the 0.8.1, 0.8.2 and 0.8.3 releases. While officially still being experimental, it is at such a mature state already, that you should consider to try it out for non-critical productive use.

Introduction

Data binding is a functionality that allows to connect data from a source to a target. The entire topic can be divided into a low-level part, called “single value binding”, and some higher-level concepts involving stores and controllers.

The main idea

The main idea of qooxdoo’s data binding component is best summarized by the following diagram. :documentation:0.8:databindingarchitecture.jpg

As you can see data binding includes five major components, which will be described in more detail in the following sections.

Data

The data part is where the raw data is stored and can be retrieved from. This can be a plain local file, a regular web server or even a web service. There are all sources of data possible depending on the implementation of the actual store.

Store

The store component is responsible for fetching the data from its source and for including it into a data model of an application. For more info about the available store components see the stores section below.

Model

The model is the centerpiece of data binding. It holds the data and acts as an integration point for the store and for the controller. Almost all models are plain qooxdoo classes holding the data in properties, which are configured to fire events on every change. Since native JavaScript arrays do not fire events when items are changed, a complementary array is added for data binding purposes. It is available with most of the native array API as qx.data.Array. But there is no need to manually write own model classes for every data source you want to work with. The stores provide a smart way to automatically create these classes during runtime. Take a look at the stores section for details.

Controller

The main task of the controller components is to connect the data in the model to the view components. Details are available in the controller section. The base layer of all controllers, the Single Value Binding is explained later.

View

The views for data binding can be almost any widget out of qooxdoo’s rich set of widgets, depending on the type of controller. qooxdoo’s data binding is not limited to some predefined data bound widgets. Please note that one of the most prominent data centric widgets, the virtual Table, currently still has its own model based layer and is not covered by the new data binding layer. The new infrastructure for virtual widgets is expected to nicely integrate the upcoming data binding layer, though.

Demos and API

You should now have a basic idea of qooxdoo’s data binding, so to see it in action, take a look at the online demos and the API reference.

Single Value Binding

The purpose of single value binding is to connect one property to another by tying them together. The connection is always in one direction only. If the reverse direction is needed, another binding needs to be created. The binding will be achieved by an event handler which assigns the data given by the event to the target property. Therefore it is necessary for the source event to fire a change event or some other kind of data event. The single value binding is mostly a basis for the higher concepts of the data binding.

Binding a single property to another property

The simplest form of single value binding is to bind one property to another. Technically the source property needs to fire a change event. Without that no binding is possible. But if this requirement is met, the binding itself is quite simple. You can see this in the following code snippet, which binds two properties of the label content together:

var label1 = new qx.ui.basic.Label();
var label2 = new qx.ui.basic.Label();
 
label1.bind("content", label2, "content");

label1 is the source object to bind, with the following three arguments to that call:

  1. The name of the property which should be the source of the binding.
  2. The target object which has the target property.
  3. The name of the property as the endpoint of the binding.

With that code every change of the content property of label1 will automatically synchronize the content property of label2.

Binding a data event to property

In some cases in the framework, there is only a change event and no property. For that case, you can bind a data event to a property. One common case is the TextField widget, which does not have a property containing the content of the TextField. Therefor you can use the input event and bind that to a target property as you can see in the example snippet. The API is almost the same as in the property binding case.

var textField = new qx.ui.form.TextField();
var label = new qx.ui.basic.Label();
 
textField.bind("input", label, "content");

As you can see, the same method has been used. The difference is, that the first argument is a data event name and not a property name.

Bind a property chain to another property

A more advanced feature of the single value binding is to bind a hierarchy of properties to a target property. To understand what that means take a look at the following code. For using that code a qooxdoo class is needed which is named Node and does have a child and a name property, both firing change events.

// create the object hierarchy
var a = new Node("a");      // set the name to „a“
var b = new Node("b");      // set the name to „b“
a.setChild(b);
 
// bind the property to a labels content
a.bind("child.name", label, "content");

Now every change of the name of b will change the labels content. But also a change of the child property of a to another Node with another name will change the content of the label to the new name. With that mechanism a even deeper binding in a hierarchy is possible. Just separate every property with a dot. But always keep in mind that every property needs to fire a change event to work with the property binding.

Bind an array to a property

The next step in binding would be the ability to bind a value of an array. That’s possible but the array needs to be a special data array because the binding component needs to know when the array changes one of its values. Such an array is the qx.data.Array class. It wraps the native array and adds the change event to every change in the array. The following code example shows what a binding of an array could look like. As a precondition there is an object a having a property of the qx.data.Array type and that array containing strings.

// bind the first array element to a label's content
a.bind("array[0]", labelFirst, "content");
 
// bind the last array element to a label's content
a.bind("array[last]", labelFirst, "content");

You can use any numeric value in the brackets or the string value last which maps to length - 1. That way you can easily map the top of a stack to something else. For binding of an array the same method will be used as for the binding of chains. So there is also the possibility to combine these two things and use arrays in such property chains.

Options: Conversion and Validation

The method for binding introduced so far has the same set of arguments. The first three arguments are mostly the same. There is a forth argument called options. This should be a map containing the options itself. In that you can specify three things currently:

  • converter: A own converter which is a function with one argument returning the converted value.
  • onSetOk: A key in the options map under which you can add a method. This method will be called on a validation case if the validation was successful.
  • onSetFail: The counterpart to onSetOk which will be called if the validation fails.

In addition there is a built in default conversion which takes care of the default conversion cases automatically. Default cases are, for example, string to number conversion. To get that working it is necessary to know the desired target type. This information is taken from the check key in the property definition of the target property.

Managing bindings

If you want to manage the bindings, there are some ways to get that. First aspect of managing is showing the existing bindings. You can find all these function on the static qx.data.SingleValueBinding class or parts of it on every object.

  • getAllBindingsForObject is a function which is in the data binding class and returns all bindings for the given object. The object needs to be the source object.
  • getAllBindings returns all bindings in a special map for all objects.

Another way of managing is removing. There are three ways to remove bindings.

  • removeBindingFromObject removes the given binding from the given source object. As an id you should use exactly the id returned during the creation of the binding.
  • removeAllBindingsForObject removes all binding from the source object. After that, the object is not synchronized anymore.
  • removeAllBindings removes all single value bindings in the whole application. Be careful to use that function. Perhaps other parts of the application use the bindings and also that will be removed!

Debugging

Working with bindings is in most cases some magic and it just works. But the worse part of that magic is, if it does not work. For that the data binding component offers two methods for debugging on the static qx.data.SingleValueBinding class.

  • showBindingInLog shows the given binding in the qooxdoo logger as a string. The result could look something like this: Binding from ‘qx.ui.form.TextField[1t]’ (name) to the object ‘qx.ui.form.TextField[1y]’ (name). That shows the source object and property and the target object and property.
  • showAllBindingsInLog shows all bindings in the way the first method shows the bindings.

Tech notes

For everyone who is interested on how that whole thing works, here are some small notes on the inside of the data binding. Every binding function maps to the event binding function. This is where the heart of the data binding lies. In that function a listener will be added to the source object listening to the change event. The key part of the listener is the following code part.

targetObject["set" + qx.lang.String.firstUp(targetProperty)](data);

In that line the listener sets the data given by the data event to the target property.

Controller

The general idea of controllers is connecting a view component to a set of data stored in a model. The kind of controller you need depends on the view component. Currently there are three types of controller available:

  • Object Controller
  • List Controller
  • Tree controller

You may miss the table controller. The currently available table will not be changed and therefore will not implement data binding features. The new virtual table, which is currently under development, will be considered for data binding.

In the following section, the selection will be discussed because it’s a common feature of the list and tree controller. The delegation mechanism is another common feature of those two controllers and will also be described. After that, each of the available controllers will be discussed in detail.

Selection

Usually the selection of view components like the tree or the list handle their selection with tree folder or list items. As a user of data binding, you don‘t want to convert the view widgets to the model widgets. Therefore, the controller does that mapping for you. There is a selection array available on the controller containing the currently selected model items. When using the selection of the controller, there is no need to deal with view widgets like ListItems. It is also possible to change the array in place and add / remove something from the selection. As it is a data array, you can use all methods defined by that array to manipulate the selection of the corresponding controller.

Delegate

The list and tree controller are responsible for creating and binding the child widgets of the views. But what if you want to use something different in the list or bind not just the label and the icon. For that purpose, the delegation offers the possibility to enhance the controller code without having to subclass it.

In total, there are three methods which relate to the topic of creating and binding the child view widgets.

configureItem

The configureItem function is the function which you can use if you just want to modify the created default widgets. This gives you the opportunity to set the labels to rich for example or modify anything else in the child widget. But this is not the place where you want to change / add the binding behavior.

bindItem

That place is the bindItem method. But you don’t want to use the single value binding all on your own and bind the stuff. Therefore, the controller offers you a method called bindProperty which takes the source path to the data, the target property name and the options for the single value binding. The other two parameters will just mapped through. But keep in mind that if you use this function, the default binding of the label and the icon is gone and the properties used for those bindings do not work anymore.

createItem

The last method named createItem gives the user the chance to add something different as child widgets to the view. In that method you just create the widget you want to see in the view and return the new item. But keep in mind that the default bindings may not work on those widgets and the code will fail. So it is always a good idea to also define its own bindings with the bindItem method.

The following code shows how such a delegate could look like.

var delegate = {
  configureItem : function(item) {
    item.setPadding(3);
  },
  createItem : function() {
    return new qx.ui.form.CheckBox();
  },
  bindItem : function(controller, item, id) {
    controller.bindProperty("name", "label", null, item, id);       
    controller.bindProperty("online", "checked", null, item, id);          
  }
};

The delegate defines, that CheckBoxes should be used as child view items. As the CheckBoxes don’t have an icon, the bindItem function needs to re-specify the bindings. It binds the name and the online property of the model to the label and checked property of the CheckBox.

Object Controller

The most simple and lightweight controller is the object controller. It connects a model object with one or more views. The data in the model can be anything a property can hold, i.e. a primitive data type like String or Number, or a reference type like a map. With that you can for instance bind views like textfields, sliders and other widgets visualizing primitive JavaScript types. But you can not only use views as targets. A target can be anything that has a property with the proper type. Take a look at the following code example to see the object controller in action:

// create two sliders
var slider1 = new qx.ui.form.Slider();
var slider2 = new qx.ui.form.Slider();
// create a controller and use the first slider as a model
var controller = new qx.data.controller.Object(slider1);
// add the second slider as a target
controller.addTarget(slider2, "value", "value");

This code snippet ensures that every value set by slider1 will automatically be set as value of slider two. As you can see, the object controller only wraps the fundamental single-value binding, trying to make its usage a little bit easier.

List Controller

A list controller could - as the name suggests - be used for list-like widgets. The supported list-like widgets in qooxdoo are List, SelectBox and ComboBox, all in the qx.ui.form package. The controller expects a data array as a data model, that contains the model objects. These objects are displayed in the list and can either have some primitive type or be real qooxdoo objects. The following code snippet shows how to bind an array of strings to a list widget:

// create the model
var model = new qx.data.Array(["a", "b", "c", "d", "e"]);
// create a list widget
var list = new qx.ui.form.List();
// create the controller
var listController = new qx.data.controller.List(model, list);

Now every change in the model array will invoke a change in the list widget.

As a unique feature of the list controller a filtering method is included. You can assign a filter function to the controller and the results will be filtered using your given function.

Tree Controller

Of course, also the tree does have its own controller. With that controller the Tree widget can automatically be filled with data from qooxdoo objects containing the data. As model nodes for the tree, only qooxdoo widgets are allowed containing at least two properties, one for holding its own children in a data array and a second one holding the name of the node which should be showed as the label of the tree folder widgets. Imagine that a model class called Node is available containing the two already mentioned properties called ch for the children and n for the name. The following code will bind a data model containing Node objects to a tree widget:

// create the model
var rootNode = new qx.Node();
rootNode.setN("root");
var childNode = new qx.Node();
childNode.setN("child");
rootNode.getCh().push(childNode);
// create the tree view
var tree = new qx.ui.tree.Tree();
// create the controller
var treeController = new qx.data.controller.Tree(rootNode, tree, "ch", "n");

After that code snippet, every change in the name or of the children will be automatically mapped into the tree view. Selecting one of the tree folders will put the corresponding Node object into the selection array of the controller.

Form Controller

Also forms do have a special controller. The form controller uses a qx.ui.form.Form as target and a Object controller for the bidirectional bindings. The usage equals to the usage of all other controllers. The main properties of it are the model and target property. Given both, the controller connects the model and the target. An additional feature of the form controller is the possibility to create the model for a given form. See the following code to get an idea of using it.

// a form is available as 'form'
// create the controller
var formController = new qx.data.controller.Form(null, form);
// create the model
var model = formController.createModel();

If you nee additional information on forms, see form handling documentation. After executing this code, the controller and the model variable do have the model available and therefore, the controller can set up the bindings.

Combining Controller

As a more advanced example we connect the selection of a tree to a list. Therefore we extend the code sample of the tree controller section.

// create a list widget
var list = new qx.ui.form.List();
// create the controller
var listController = new qx.data.controller.List(null, list, "n");
// bind the selection of the tree to the list
treeController.bind("selection", listController, "model");

The example shows how the controller can work pretty well together with the single value binding. The trick is not to set the model of the list controller at creation time. The model will be set by the single value binding from the tree controllers selection. This works because the selection will be provided as data array.

Stores

The main purpose of the store components is to load data from a source and convert that data into a model. The task of loading data and converting the data into a model has been split up. The store itself takes care of loading the data but delegates the creation of model classes and instances to a marshaler.

The only marshaler currently available is for JSON data and therefore, the only data store available is a JSON store. Both will be described in detail in the following sections.

JSON Marshaler

NOTE: This class should only be used if you want to write your own data store for your own data types or request.

The marshaler takes care of converting JavaScript Objects into qooxdoo classes and instances. You can initiate each of the two jobs with a method.

toClass

This method converts a given JavaScript object into model classes. Every class will be stored and available in the qx.data.model namespace. The name of the class will be generated automatically depending on the data which should be stored in it. As an optional parameter you can enable the inclusion of bubbling events for every change of a property. If a model class is already created for the given data object, no new class will be created.

toModel

The method requires that the classes for the models are available. So be sure to call the toClass method before calling this method. The main purpose of this method is to create instances of the created model classes and return the model corresponding to the given data object.

JSON Store

The JSON store takes an URL, fetches the given data from that URL and converts the data using the JSON marshaler to qooxdoo model instances, which will be available in the model property after loading. The state of the loading process is mapped to a state property. For the loading of the data, a qx.io.remote.Request will be used in the store.

The following code shows how to use the JSON data store.

var url = "json/data.json";
var store = new qx.data.store.Json(url);

After setting the URL during the creation process, the loading will begin immediately. As soon as the data is loaded and converted, you can access the model with the following code.

store.getModel();

Combining with controllers

As described in the section above, you can access the model in the property after loading. The best solution is to use the model with a controller and then bind the the model properties with single value binding together. The code for this could look something like this.

store.bind("model", controller, "model");

Using the single value binding, the binding handles all the stuff related with the loading of the model data. That means that the data will be available in the controller as soon as its available in the store.

How to get my own code into the model?

What if you want to to bring your own code to the generated model classes or if you event want to use your own model classes? Thats possible by adding and implementing a delegate to the data store. You can either

  • Add your code by supporting a superclass for the created model classes.
  • Add your code as a mixin to the created model classes.
  • Use your own class instead of the created model classes.

Take a look at the API-Documentation of the qx.data.store.IStoreDelegate to see the available methods and how to implement them.

Information

Last modified:
2009/09/25 20:07 by axelhinrichs

Account

Not logged in

 
 

Rich Ajax Platform (RAP)

RAP uses qooxdoo, Java and the Eclipse development model to build rich web applications. Read more...

qooxdoo Web Toolkit (QWT)

Similar to GWT this framework allows to create impressive qooxdoo applications just using Java. Read more...

Pustefix

Pustefix is a MVC-based web application framework using Java and XML/XSLT. Read more...

 
SourceForge.net Logo

Bad Behavior has blocked 0 potential spam attempts in the last 7 days.