Cascading Comboboxes in Webix

Reading time: 3 mins

If you want to create powerful yet simple forms for complex input, join me. I will show you how to create cascading comboboxes, where the list of options of one combobox depends on the selection in the previous one. This way users can input complex hierarchical data in a convenient way.

Cascading Comboboxes in Webix

Client-side Option Filtering

If comboboxes have a few options with relatively simple dependencies, it is better to load them once and then filter out the relevant ones.

Let’s create two comboboxes with countries and towns and prepare the lists of options for them. Both lists can be linked through IDs:

// countries
[
  { "id":"1", "value":"Italy" },
  ...
]

// cities
[
  { "id":"1", "value":"Rome", "country":"1" },
  ...
]

At first, all possible ‘town’ options are loaded into the second combobox. They will be filtered depending on the value of the first combobox. I will handle the onShow event of the dependent option list and call the filtering method that will compare the IDs and return matches:

{
  view:"richselect",
  label:"Select a country",      
  id:"country",
  options:countries
},
{
  view:"richselect",
  label:"Select a city",
  id:"city",
  options:{
    data:cities,
    on:{
      onShow(){
        let country = $$("country").getValue();
        if (country){
          this.getList().filter(obj => obj.country == country)
        }
      }
    }
  }      
}

I will also need to clear the previous city selection after a new country is selected in the first combobox:

{
  view:"richselect",
  label:"Select a country",      
  id:"country",
  options:countries,
  on:{
    onChange(){ $$("city").setValue(); }
  }
}

Cascading comboboxes in Webix

Live demo >>

Reloading Options from Server

If there are too many options and they need complex filtering, it is better to filter options on the server side and reload combobox options. New cities for the second combobox will be loaded when a country is selected.

I will change the previous demo and replace filtering options with reloading:

{
  view:"richselect",
  label:"Select a city",
  id:"city",
  options:{
    on:{
      onShow(){
        let country = $$("country").getValue();
        if (country){
            this.getList().clearAll();
            this.getList().load("https://api.myjson.com/bins/"+country);
        }
      }
    }
  }
}

Live demo >>

This solution works fine. Yet, if you want to reuse it for several comboboxes, you will have to copy this code and paste it with a different ID of the master combo. There is a better way: I can create a new component that will know how to link to other combos. For example, let’s create such a component on the base of Richselect:

webix.protoUI({
  name:"dependent",
  $cssName:"richselect"
}, webix.ui.richselect);

A dependent combobox will have two extra configuration attributes:
– the ‘master’ property that will store the ID of the combobox it depends on;
– the ‘dependentUrl’ property that will store the path to the script with unfiltered options.

{
  view:"richselect",
  label:"Select a country",      
  id:"country",
  options:"https://api.myjson.com/bins/1d9x9m",
},
{
  view:"dependent",
  master:"country",
  dependentUrl:"https://api.myjson.com/bins/",
  label:"Select a city",
  id:"city",
  options:[]
}

Next I will add the option loading logic for the dependent richselect. When a country is selected in the master, the dependent combo will be reset and a new option list will be loaded. I will attach an event handler to the master combobox for this:

webix.protoUI({
  name:"dependent",
  $cssName:"richselect",
  $init:function(config){
    var master = $$(config.master);
    master.attachEvent("onChange", (newV) => {
      this.setValue();
      this.getList().clearAll();
      if (newV)
        this.getList().load(config.dependentUrl+newV);          
    });
  }
});

The same can be done with any selection widget (select, combo, multiselect, etc.), so it is a good idea to store this functionality separately and then use it for any other component:

const LinkedInputs = {
  $init:function(config){
    var master = $$(config.master);
    master.attachEvent("onChange", (newV) => {
      this.setValue();
      this.getList().clearAll();
      if (newV)
        this.getList().load(config.dependentUrl+newV);          
    });
  }
};

webix.protoUI({
  name:"dependent",
  $cssName:"richselect"
}, LinkedInputs, webix.ui.richselect);

Live demo >>

This is how you can reuse LinkedInputs described in the above code for creating cascading combo views:

webix.protoUI({
  name:"dependent",
  $cssName:"combo"
}, LinkedInputs, webix.ui.combo);

You can also create a chain of cascading comboboxes. For example, let’s add one more combo with a list of streets of the selected city:

...
{
  view:"dependent",
  master:"city",
  dependentUrl:"https://api.myjson.com/bins/",
  label:"Select a street",
  id:"street",
  options:[]
}

chain of Webix comboboxes

Live demo >>

Conclusion

It is also a good idea to hide or disable dependent comboboxes until an option is selected in the parent combobox. This can be easily done with the hide()/show() and disable()/enable() methods of Webix controls.

By linking form controls, you can create convenient forms for inputting complex data with dependencies. This can be done with practically all selection widgets and switches (all variations of selection controls, checkboxes, radio buttons, segmented buttons, etc.), with which users can input a limited number of values.

You can read more blog articles about Webix Form: