ExtJS ComboBoxes – Part 3

Customizing the fields of a combobox can often prove problematic. The valueField configuration option, in particular, adds a lot of extra complexity and yet in the majority of cases its use is totally unnecessary. However, when it's used correctly, it can greatly enhance the power of a combobox to represent more sophisticated data in an intuitive way.

Using A Different Field

As we saw in the last article, the default field for a combobox is called text. This can be changed using the displayField setting.

        Ext.create('Ext.form.field.ComboBox', {
            displayField: 'utensil',
            store: ...
        });
    

The code above will require the records in the store to have a field called utensil but it doesn't really matter how the store is created or populated. Here's a complete example that uses a store with inline data:

        Ext.create('Ext.form.field.ComboBox', {
            displayField: 'utensil',
            queryMode: 'local',

            store: {
                fields: ['utensil'],

                data: [
                    {utensil: 'Knife'},
                    {utensil: 'Fork'},
                    {utensil: 'Spoon'}
                ]
            }
        });
    

This example is a little contrived, it certainly isn't the easiest way to create a cutlery combobox, but being able to choose which field to use proves very useful in real applications.

Retrieving Or Setting The Value

Being able to select a value in a combobox isn't much use unless you can access that value programmatically. To retrieve the current value just call getValue:

        var combo = Ext.create('Ext.form.field.ComboBox', {
            store: ['Red', 'Green', 'Blue']
        });

        Ext.create('Ext.button.Button', {
            text: 'getValue',

            handler: function() {
                alert('getValue: ' + combo.getValue());
            }
        });
    

One important thing to note with this example is that the user can type in arbitrary values, not just the values in the list. We'd have to use forceSelection: true or editable: false if we wanted to restrict the user to just the values in the list.

Conversely, setting the value is done by calling setValue:

        var combo = Ext.create('Ext.form.field.ComboBox', {
            store: ['Red', 'Green', 'Blue']
        });

        Ext.create('Ext.button.Button', {
            text: 'Red',

            handler: function() {
                combo.setValue('Red');
            }
        });

        ...
    

As we saw in the first article in this series, we can also set the initial value of the combobox using the value config option:

        Ext.create('Ext.form.field.ComboBox', {
            store: ['Red', 'Green', 'Blue'],
            value: 'Green'
        });
    

At this point you could be forgiven for wondering why I'm even bothering to introduce something so simple. It's just a getter and a setter, right? Don't worry, all will become clear (or maybe less clear) very soon.

Value Field

In some cases it can be desirable for the value returned by getValue to be different from the text displayed in the combobox. For example, we may want getValue to return the selected record's id but for the text in the combobox to be a more user-friendly name. The following example does just that, using the valueField config option.

        Ext.create('Ext.form.field.ComboBox', {
            displayField: 'name',
            editable: false,
            queryMode: 'local',
            valueField: 'id',

            store: {
                fields: ['id', 'name'],

                data: [
                    {id: 'scheme1', name: 'Red Mist'},
                    {id: 'scheme2', name: 'Green Envy'},
                    {id: 'scheme3', name: 'The Blues'}
                ]
            }
        });
    

Now the combobox is using two fields. The setting valueField: 'id' specifies the field to use for the underlying value whereas the setting displayField: 'name' specifies which field contains the text to display to the user.

Calls to getValue now return the id for the record. Similarly, any calls to setValue should pass the id rather than the name. The same applies for setting the value config:

        Ext.create('Ext.form.field.ComboBox', {
            displayField: 'name',
            ...
            value: 'scheme2', // Set the initial value to scheme2, 'Green Envy'
            valueField: 'id',

            store: {
                fields: ['id', 'name'],

                data: [
                    ...
                    {id: 'scheme2', name: 'Green Envy'},
                    ...
                ]
            }
        });
    

Separating the displayed value from the underlying value can cause some interesting problems, especially for comboboxes that use queryMode: 'remote'. There are quite a few nasty edge cases but most of them have the same root cause. The combobox needs a matching record to map from the underlying value to the display text, or vice-versa, but for remote querying the store won't necessarily contain the relevant record at the point the combobox needs it.

If you're specifying a valueField then make sure you also specify queryMode: 'local'. You should also have either editable: false or forceSelection: true. If for some reason you don't want to include these other settings then you may want to consider avoiding using a valueField.

Two-Field Store Shorthand

For working with inline data there's a shorthand for defining the combobox's fields without the need for all the explicit configuration options. We've already seen inline data specified as an array of string values, like this:

        Ext.create('Ext.form.field.ComboBox', {
            ...
            store: [
                'Red Mist',
                'Green Envy',
                'The Blues'
            ]
        });
    

If, instead, we pass pairs of values in arrays then the first entry is used for the underlying value and the second entry is used as the display text:

        Ext.create('Ext.form.field.ComboBox', {
            ...
            store: [
                ['scheme1', 'Red Mist'],
                ['scheme2', 'Green Envy'],
                ['scheme3', 'The Blues']
            ]
        });
    

The field names for the store will be automatically generated and the displayField and valueField will be configured to match the names used by the store.

Raw Value

The raw value for a combobox is the text displayed in the combobox's input element. The method getRawValue allows direct access to this raw value even when the combobox has a valueField.

        var combo = Ext.create('Ext.form.field.ComboBox', {
            editable: false,

            store: [
                ['type1', 'Single File'],
                ['type2', 'Double Cream'],
                ['type3', 'Triple Jump']
            ]
        });

        Ext.create('Ext.button.Button', {
            text: 'getValue & getRawValue',

            handler: function() {
                alert('getValue: ' + combo.getValue() + '\ngetRawValue: ' + combo.getRawValue());
            }
        });
    

It can be useful to think of getRawValue as returning the value of the displayField for the selected record. While interpreting it this way can help your intuition it's important not to be led astray. Even if there isn't a currently selected record there can still be a raw value: it's just the text in the combobox.

In many scenarios you'll find that it's a largely arbitrary choice between getValue and getRawValue. As a general rule you should favour using getValue unless you have a good reason to do otherwise.

Finding The Record

In some cases it can be useful to access the selected record in a combobox. You might expect there to be a getSelectedRecord method but unfortunately, at the time of writing, there isn't such a method. However, it is still relatively simple to access the selected record. The combobox method findRecordByValue will search for a record in the store based on the configured valueField, like this:

        var combo = Ext.create('Ext.form.field.ComboBox', {
            valueField: 'sku',
            ...
        });

        ...

        var record = combo.findRecordByValue('376N');
    

In this example we are attempting to access a record with a sku value of '376N'. If no such record is found it will return false. If we wanted to access the currently selected record we'd use something like this:

        var record = combo.findRecordByValue(combo.getValue());
    

Here's a working example that uses country data:

        var combo = Ext.create('Ext.form.field.ComboBox', {
            editable: false,
            queryMode: 'local',
            valueField: 'code',

            store: {
                fields: ['code', 'text', 'continent'],
                ...
            }
        });

        Ext.create('Ext.button.Button', {
            text: 'findRecordByValue',

            handler: function() {
                var code = combo.getValue(),
                    record = combo.findRecordByValue(code);

                if (record) {
                    alert('Code: ' + code + '\nContinent: ' + record.get('continent'));
                }
                else {
                    alert('Nothing selected');
                }
            }
        });
    

The data for this example is loaded via an AJAX request and looks a little like this:

        [
            {"text": "Belgium", "continent": "Europe", "code": "be"},
            {"text": "Bulgaria", "continent": "Europe", "code": "bg"},
            ...
        ]
    

The displayField hasn't been specified so it will default to 'text'. The underlying values in this example are the two-letter country codes. The continent field isn't used by the combobox itself but the button handler pulls the continent out of the selected record.

In a similar vein, the method findRecordByDisplay can be used to find a record by the displayField. There is also a method called findRecord that can be used to find a record based on an arbitrary field. If none of these meet your needs then you can always just interact with the store directly using combobox.getStore().