Data Binding

In a nutshell, data binding is a technique that allows you to connect components establishing master-slave relations between them. The technique supposes that the master component serves as the data source for the slave(s) when any record or value is selected in the master. Thus the master must always have a DataStore or DataValue. The slave can be a single-record component (e.g. form or template) or a data component as well.

Basic principles

Binding components

To bind two components you need to call the bind method on the slave component, passing the master one as a parameter. One of the common use cases is to bind a form to a data component e.g. List:

$$("form1").bind($$("list1"));

Now when you select an item from the list, the form will be populated with the item data. Keep in mind that the name property of the input fields must coincide with the data field names in the data component:

// data component
{ name:"Jane", age:23 }
// form
{
    view:"form", id: "form1",elements:[
        { name:"name", view:"text" },
        { name:"age", view:"text", type:"number" }
    ]
}

Saving changes to the master

Changes in the slave component will affect the master. If you edit the data in the form and then save it, the list item will be updated. To save the changes, call the save method of the form. If the form was not filled with the data from the master when the save method was called, the form will add a new element into the master.

Note that only bound forms have this method:

$$("form1").save();

Related sample:  Creating Basic App: Step 3

Related sample:  Binding to a Native HTML Form

You can also save some extra data together with the values from the form. For that you should get the form values, add necessary properties there and pass the resulting object to the form.save() method:

const values = form.getValues();
values.myfield = "My value";
form.save(values);

Disconnecting components

If you no longer want to connect the bound views (components), you can unbind the slave component:

$$("form1").unbind();

How it works

You can think of binding as syntactic sugar for a more straightforward approach to connect components via events. For instance, the above example of binding form to a list can be reproduced with:

// select an item in the list and fill the form
$$("list1").attachEvent("onAfterSelect", function(id){
    var item = this.getItem(id);
    $$("form1").setValues(item);
});
 
// save form data into the list
view:"form", id:"form1", elements:[
    // ...
    {
        view:"button", value:"Save", click:function(){
            var item = this.getFormView().getValues();
            $$("list1").updateItem(item.id, item);
        }
    }
]

Consider this approach, if you develop with Webix Jet, as a form and its master are likely to be in the different files so classic binding is impossible.

Binding two forms to the same master

Binding two forms to the same master component is an uncommon practice, yet it is possible. By default, if you bind two forms to the master and try to save each of them separately, the data from the previously saved one will be lost.

There are two possible solutions:

1) get values from both forms via the getDirtyValues method that returns only the updated data and unite them with extend:

const v1 = $$("form1").getDirtyValues();
const v2 = $$("form2").getDirtyValues();
$$("form1").save(webix.extend(v1, v2, true) ); // combine values from both forms

2) use the saveBatch method that makes simultaneous saving of several forms together with getDirtyValues.

$$("datatable1").saveBatch(function(){
    $$("form1").save();
    $$("form2").save($$("form2").getDirtyValues());
});

Related sample:  Binding Two Forms to the Same Master

The getDirtyValues method returns only the fields that were changed in the form.

Preventing from Undefined Values

This concerns only those slave data components that have the template property in their configuration. In a form and html form binding is set according to the names of their inputs.

When no selection is made in the master component, the slave component shows undefined values. You can set the default values that should be displayed when nothing is selected. Use the defaultData property of the master:

webix.ui({
    rows:[
        {
            view:"list",
            id:"list1",
            template:"#rank#.#title#",
            // data: ...
            defaultData:{
                rank:"0",
                title:"default Item"
            }
        },
        { view:"template", id:"template1" template:"#rank#.#title#" }
    ]
});
 
$$("template1").bind($$("list1"));

Related sample:  Binding: Default Data

Using Binding for Server-side

As a rule, a slave component does not communicate with the server side directly. It receives data from its master, and then the changed data are sent first to the master that handles communication with the server. However, you can get the server-side data for the slave based on master selection with the help of the dataFeed functionality.

webix.ui({
  view:"form",
  id:"form1",
  // ...config
  dataFeed: "slave_data.php"
});
 
$$("form1").bind("datatable1")

Related sample:  Datatable: Binding with Form

dataFeed defines the URL that will be used by the slave to send a request when selection in the master component changes.

The functionality works the same for slave forms and collections (data components), yet the URL parameters sent with a request differ:

  • for forms: "action=get&id="+obj.id
  • for data components: "filter[id]="+obj.id

where obj is the selected data item in the master component.

Single-value component as master

The master can be a single-value component like Richselect, Datepicker, etc. When it changes its value, the value is passed to the slave.

$$("template1").bind($$("calendar1"));

Related sample:  Data Binding: Template Bound to Calendar

Multiple-value component as slave

In this case, binding is possible only if the slave component has DataStore or TreeStore (DataTable, Tree, List, Chart, etc).

When a slave is a multiple-value component there are two scenarios possible:

  • data is parsed to the slave beforehand and gets filtered upon selection of a record in the master
  • the slave has no data initially and gets populated upon selection of a record in the master.

Filtering and parsing patterns can be provided via the special rules and techniques described further.

Rules for Binding Pattern

You can provide a pattern, according to which the records in the slave component are either filtered or being populated. It is done via a special rule that is being passed to the bind method as the second parameter.

Filtering data in slave

When you bind two data components, you can provide a pattern according to which preparsed data are filtered in the slave. The pattern can be a string (a record field to filter data by) or a function. The pattern is passed to the bind method as the second parameter.

Filtering slave data by the title field

// input receives value stored in the “title” column
$$("$text1").bind($$("$datatable1"), "title");

Related sample:  Binding: Filtering by a Custom Field

If passed as a function the pattern receives the following parameters:

  • slave - the slave data object
  • master - the selected record in the master (data object or value depending on the type of the component).

The function compares two values and returns true or false depending on whether the current item should be displayed.

Binding multiple-value components

$$("grid2").bind($$("grid1"), function(slave, master){
// if no record is selected, do not show slave items
if(!master) return false;
// slave datatable displays only records with movie field equal to master record ID
    return master.id == slave.movie;
});

Related sample:  Data Binding: Filter Linked Table via Binding Rule

Note that in this case the master is a multiple-value component and the master parameter is the whole data record (object). Thus you must specify a field to filter data by (master.id in this case).

Meanwhile, when the master is a single-value component, the master parameter is its value, so filtering is based upon this value:

List data are filtered depending on selected option in Richselect

// here master is an id of the selected option (e.g. 1)
$$("list1").bind($$("richselect1"), function(slave, master){
  return slave.category == master;
});

Related sample:  Binding: Filter by Binding Rule

Parsing data into slave

Another pattern to display data in the slave is parsing them in it. It assumes that the slave component is initially empty and gets populated upon selection in the master. It also allows you to decide which data should be pushed to the slave. The rule can contain the following flags:

  • $level - to push only the immediate children of a selected item (only for hierarchical data)
  • $data - to push data from a specified field (the field is specified via the source parameter of the bind method).

Let’s bind a DataTable to a Tree and push the immediate children of the selected item into the DataTable:

$$("grid1").bind($$("tree1"), "$level");
 
// where the tree data of the selected node is something like
{ id:"3", value:"Node 3", data:[
    // will be shown in slave component
    { id:"3.1", value:"Subnode 3.1" },
    { id:"3.2", value:"Subnode 3.1" }
]}

Related sample:  Tree Data Binding

Specifying custom field to populate the slave from

By default, in Tree-like components the children of a data item are introduced by the data key in all the supported data formats. If you want to push data stored by a different key, you can pass its name or provide a function to populate the slave manually as the 3rd parameter of the bind method.

Source as a string

If passed as a string, the source expects the name of the field to get data from. Let's bind a DataTable to a Tree and fill the slave with the data stored in the records field of Tree items:

$$("grid1").bind($$("tree1"), "$data", "records");
 
// where the tree data of the selected node is something like
{ id:"3", value:"Node 3", records:[
    // will be shown in slave component
    { id:"3.1", value:"Subnode 3.1" },
    { id:"3.2", value:"Subnode 3.1" }
]}

Related sample:  Tree Data Binding: Subdata

The same technique can be implemented with linear data that contain subdata:

{
 view:"list",
  id:"list1",
  data: [
    {value:"1", data:["a", "b"]},
    {value:"2", data:["c", "d"]}
  ],
// ...
}
 
$$("grid1").bind( $$("list1"), "$data", "data");

Related sample:  Data Binding: Linear Data with Subdata

Source as a function

Source can be passed as a function, where you can populate the slave manually. The function takes two parameters:

  • obj - data object of the selected record in master
  • source - master component.

For example, let's pass a function that will push both data and records from the selected node:

$$("grid2").bind( $$("tree1"), "$data", function(obj, source){
    if (!obj) return this.clearAll();
    var fulldata = [].concat(source.data.getBranch(obj.id)).concat(obj.records);
    this.data.importData(fulldata, true);
});

Related sample:  Tree Data Binding: Subdata

Here we get item children with the help of a getBranch method, combine them with data set by the records key and import the resulting array into slave datatable.

Binding with DataCollections

You can bind components to view-less data collections.

var dataCollection = new webix.DataCollection({
  data: big_film_set
});
 
$$("form1").bind(dataCollection);

There is one difference from binding to a data component. Binding is based on the selection. Since there is no visual selection in DataCollection, it cannot focus on the item visually. Thus when selecting an item in the visual component synchronized to it you need to set a virtual cursor on the corresponding item in the master DataCollection.

For that call the setCursor() method passing the ID of the selected data record as a parameter:

$$("list1").attachEvent("onAfterSelect", function(id){
  data.setCursor(id);
});

Related sample:  Data Syncing: Collections and Widgets

The cursor stores the ID of a data item that is currently active in the master component (data collection in this case).

You can also get cursor position with the help of the dedicated getCursor() method:

const cursor = master.getCursor();

The cursor can be deleted to remove the current bind link:

master.setCursor(null);

If a form is bound to the list, and the list is synced with a DataCollection, removing the cursor from the collection will clear the form.

Binding events

When you bind components, the slave component gets three events:

  • onBindApply – fires the moment binding is applied
  • onBindRequest - fires when the component is ready to receive data from the master component
  • onBindUpdate – fires when the value in the slave view changes and save() is called to update master.
$$("datatable1").attachEvent("onBindUpdate", function() {
  webix.message("Data updated in the master");
 
  const values = $$("form1").getDirtyValues();
  $$("textarea1").setValue(JSON.stringify(values, "", "\t"));
});

Related sample:  Data Binding: Binding Events

Back to top