Firstly, apologies for the lateness of this follow up post!The aim of this post is to describe how we can use Select Fields (combo-boxes) to create a form whose options change based on the previous selections made by the user. So basically when a user makes a selection from the first Select Field we want the next one’s possible values to change, reflecting the choice made.
The scenario we will use is a simple form where the User can choose a Country and then a City from two Select Fields which could be used in a form where a User completes their Address.

As with all our tutorials there is a Tutorial Package that contains all the files you need to try the examples and follow the tutorial step by step. If you click on the Step’s header you can view the demo for that step.
Step 1 – Create the formI have thrown together a quick form to demonstrate this idea which consists of 2 Models (Country & City), 2 Stores (to contain the Country and City data) and a form with 2 Select Fields linked to the Country and City stores. If you view the Step 1 demo page (by clicking the Step 1 title) you will see that the first Select Fields contains 4 Countries and the second contains 8 Cities.
DynamicForms.MyForm = Ext.extend(Ext.form.FormPanel, {
initComponent: function(){
Ext.apply(this, {
floating: true,
width: 350,
height: 370,
centered: true,
modal: true,
hideOnMaskTap: false,
items: [{
xtype: 'selectfield',
label: 'Country',
store: countryStore,
displayField: 'CountryName',
valueField: 'CountryID'
}, {
xtype: 'selectfield',
label: 'City',
store: cityStore,
displayField: 'CityName',
valueField: 'CityID'
}]
});
DynamicForms.MyForm.superclass.initComponent.call(this);
}
});
Now, at a basic level this form is fine – people should know what Country the city they live in is and they should be able to pick their City from an alphabetical list of cities. However, we know we’re better than this because this will no doubt cause some problems with data integrity later on (and we also know that, in general, we have to assume that all end-users are not very smart…) so we should make it easier and more bullet-proof. So, once the User has picked a Country we’re only going to give them the option to select a City that we already know is that chosen Country.
Like we did in the first Part we are going to hook into the ‘change’ event of the first Select Field and filter the Store (containing the Cities) attached to the second Select Field. This is especially easy because we have our Models set up to have nice relationships between them meaning we can pick up the CountryID of the selected Country and apply that to the City store to get a list of Cities. Easy!
Firstly we’ll listen for the ‘change’ event as we can see below. This code is placed below the initComponent superclass call and we also add the onCountryChange method that will be executed when the event happens:
...
initComponent: function(){
// config omitted
DynamicForms.MyForm.superclass.initComponent.call(this);
this.items.get(0).on({
change: this.onCountryChange,
scope: this
});
},
onCountryChange: function(selectField, value){
}
...
Next we need to implement what happens when a new Country is chosen, i.e. the contents of our onCountryChange method.
In pseudocode this is what we want to do:
- Remove any Filter that is currently on the City select field
- Add a new Filter based on the newly selected Country ID
- Select the first City that appears in that list by default
So, how do we actually do that?
Remove any Existing Filters
Firstly, we grab a reference to the City Select Field component using the ‘get‘ method of the items’ MixedCollection.
var citySelectField = this.items.get(1);
The ‘this‘ reference in our onCountryChange method refers to our GridPanel. This is accomplished by using the ‘scope‘ config option when setting up the listener, which we assigned to the ‘this’ value within the initComponent method.
We can use this config option to force the scope of the listeners which would otherwise be executed in the context of the component that fired them.
Next we remove the existing filter that is on the City select field. We do this by calling the ‘clearFilter‘ method of the Select Field’s store which we can access via the Select Field’s public property ‘store‘.
citySelectField.store.clearFilter(); // remove the previous filter
Filter the Cities Based on the Selected Country
The second of the two parameters that are passed into our ‘onCountryChange‘ method is the value that was selected. This is the Model instance’s CountryID because of the way we configured the Countries Select Field. If you look back to the first code snippet we set the ‘valueField‘ config option to ‘CountryID’ which means that when calling methods such as ‘getValue‘ or ‘setValue‘ on this field you will be dealing with the CountryID of the selected Model instance.
Now that we know which Country has been selected, and the CountryID of it, we can use that to filter the Cities Select Field’s store by that value. This is a simple case of calling the ‘filter‘ method on the City store passing in the Field we want to filter on, and the Value to filter with. For anyone with SQL experience, this is performing a simple WHERE-style filtering, i.e. .filter(‘CountryID’, 1) <=> WHERE CountryID = 1.
citySelectField.store.filter('CountryID', value);
Make a Default Selection
The final step is to make sure that we keep things tidy. If you run your code right now you will find that the filtering works a treat but after selecting a City and then changing your Country, the selected City remains. We can’t have that because then an incorrect City could be submitted for the selected Country. We can fix this by automatically selecting the first City in the newly filtered list or, if there are no Cities available for the chosen Country, just set the value to a blank.
First we grab the first City in the filtered Store.
var firstCity = citySelectField.store.getAt(0);
If there are no Cities present then the firstCity variable will be set to ‘undefined’ and so we can use this as a check before we set the default City value because if there isn’t any we want to reset the Select Field to a blank string.
// if not undefined we can set the value to the first record's CityID
if(firstCity){
citySelectField.setValue(firstCity.data.CityID);
} else {
citySelectField.setValue('');
}
That’s all we need to put in our change event handler and the full method looks like this:
onCountryChange: function(selectField, value){
var citySelectField = this.items.get(1);
citySelectField.store.clearFilter(); // remove the previous filter
// Apply the selected Country's ID as the new Filter
citySelectField.store.filter('CountryID', value);
// Select the first City in the List if there is one, otherwise set the value to an empty string
var firstCity = citySelectField.store.getAt(0);
if(firstCity){
citySelectField.setValue(firstCity.data.CityID);
} else {
citySelectField.setValue('');
}
}
The last bit of code we need is to force all this to happen when the form first loads because otherwise all the Cities are present in the list even though no Country is selected. We can do this by simply calling the ‘onCountryChange‘ method ourselves at the end of the initComponent method. We pass in a reference to the Country Select Field and a value (CountryID) of 1, which is the default selection.
this.onCountryChange(this.items.get(0), 1);
And there we have it, a simple chained Select Field form.
This technique can improve your form in a couple of ways:
We reduce the possibility of incorrect data hugely.
There will always be someone who picks their Country as England and their City as Cardiff (obviously, being good app designers, we’re checking all this again on the serverside but we want to make that code redundant for the average, non-hacking user) so preventing them from doing this in the first place is an instant win.
It enhances the user experience.
In our simple example the lists are only a few of entries long meaning finding the right entry is fairly easy, but imagine dealing with much larger lists and the problem is multiplied, leading to frustrated and angry users.
I hope this quick tutorial has helped you to add some more clever dynamics to your forms. If you have any ideas for other ways to make forms a little more clever then leave a comment and we will see if we can come up with wee tutorial explaining how to do it.