combo

Ext JS 4: Adding ComboBoxes via Remote Configuration in MVC

In an Ext JS 4 MVC application I am currently working on, I need to programatically add comboboxes to a form.  The list of comboboxes varies depending on the selected user.  In addition, the store backing each of the comboboxes is different.  Here is my approach.

The Model
In my case, although the stores might have different data, they have the same fields.  It is a generic label/value set up.

Ext.define('MyApp.model.LabelValue', {
	extend: 'Ext.data.Model',
	fields: [
		'label',
		'value'
	]
});

The Store
Because the stores are so similar for each combo, I created a store that was specific to my case, but generic enough to be used for all the combo boxes. The important part is the alias that is part of the store definition ‘store.mycombostore’. This will allow me to reference the store type, but ensure a new store is created for each combobox. This helpful piece of information is from the comments on the store config for ComboBox, found at Ext.form.field.ComboBox.

Ext.define('MyApp.store.MyComboStore', {
	extend: 'Ext.data.Store',
	alias: 'store.mycombostore',
	model: 'MyApp.model.LabelValue',
	autoLoad: false,

	proxy: {
		type: 'ajax',
		url : 'put/your/url/here',
		reader: {
			type: 'json',
			root: 'data'
		}
	}
});

The View
To reduce the amount of remote configuration that I need to generate I defined a custom combobox with all the static configurations that I wanted. I have a few other things going on in my combobox, like using my Remote To Local plugin. I also overrode beforeBlur to allow the user to delete and basically re-empty the combobox, and I’m setting some params before the store loads. I initially listened for the before store load event in the controller after adding the combobox configs to my form, but putting it right in the custom combobox was cleaner. Not sure if it violates MVC or not…

Ext.define('MyApp.view.MyCombo', {
	extend: 'Ext.form.field.ComboBox',
	alias: 'widget.mycombo',

	requires: [
		'MyApp.store.MyComboStore',
		'Ext.ux.form.field.RemoteToLocalComboBox'
	],

	allowBlank: true,
	forceSelection: true,
	queryMode: 'remote',
	minChars : 1, //begin query after user types 1 char
	msgTarget: 'side',
	queryDelay: 10,
	triggerAction: 'all',
	typeAhead: true,
	typeAheadDelay: 250,

	labelWidth: 200,
	width: 450,

	name: 'roleTemplates',
	displayField: 'label',
	valueField: 'value',

	plugins: [
		{ptype:'remotetolocalcombo'}
	],

	beforeBlur: function () {
		if (this.getRawValue().length === 0) {
			this.lastSelection = [];
		}
		this.doQueryTask.cancel();
		this.assertValue();
	},

	initComponent: function() {
		var me = this;

		Ext.applyIf(me, {
			emptyText: 'Process Access Request...',
			loadingText: 'Loading...',
			store: {type: 'roletemplatesremote'}
		});
		me.callParent(arguments);
		me.getStore().on('beforeload', this.beforeTemplateLoad, this);
	},

	beforeTemplateLoad: function(store) {
		store.proxy.extraParams = {
			moduleId: this.moduleId
		}
	}
});

The Controller
So, in my controller I’m loading all my “user” data to populate the form. As part of my AJAX JSON response, I have the remote configuration:

[
	{
		"xtype":"mycombo",
		"fieldLabel": "Module One",
		"moduleId":"module1"
	},
	{
		"xtype":"mycombo",
		"fieldLabel": "Module Two",
		"moduleId":"module2"
	}
]

And here is the AJAX request and response handling. The Ext.container.Container doc has a section on “Adding via remote configuration” that explains what to do.

Ext.define('MyApp.controller.UserDetail', {

	//...  all the standard controller stuff
	
	loadUser: function() {
		Ext.Ajax.request({
			url: 'your/url/here',
			jsonData: {
				userId: userId
			},
			scope: this,
			callback: this.loadUserCallback
		});
	}
		
	loadUserCallback: function(options, success, response) {
		// userDetail is the view where I am adding the comboboxes.  The
		// new comboboxes will be added to a fieldContainer.
		var userDetail = this.getUserDetail(), 
			responseJson,newCombos;
		if (success) {
			responseJson = Ext.decode(response.responseText);
			if (responseJson.success === true) {
				try {
					newCombos = eval(responseJson);
					userDetail.down('#comboFieldContainer').add(newCombos);
				} catch (e) {
					userDetail.down('#comboFieldContainer').add({
						xtype: 'component',
						html:'No pending access requests'
					});
				}
			} else {
				// showErrorResults is a function that I have to 
				// display an error message on my view
				this.showErrorResults(responseJson.message);
			}

		} else {
			// showErrorResults is a function that I have to 
			// display an error message on my view
			this.showErrorResults('Something went wrong.');
		}
	},
	
	//...
});

I have a bit more going on so this is a little stripped down, but looking at the Ext JS docs should clarify what is needed. Basically the AJAX request returns JSON w/the configuration objects. In the response handling, the JSON is eval’d to objects which are then added to the view. By creating a few custom components I am able to dynamically add to objects to my view, without having tons of configuration determined server-side.

Here is a picture. It’s not too exciting, but it’s exactly what I want, two user-specific comboboxes determined server-side at runtime.

RemoteConfigCombos

Version Info:
Ext JS 4.1.1

Advertisements

Ext JS 4: Empty Value in a ComboBox

Often, in an Ext JS combobox, it is difficult to go back to an empty value once you have selected an item, particularly if “forceSelection” is set to true.  Here is my roundup of alternative solutions found from around the web…

1. Override beforeBlur

The solution from  http://www.sencha.com/forum/showthread.php?182119-How-To-Re-Empty-ComboBox-when-forceSelection-is-Set-To-TRUE overrides beforeBlur on the combbox to clear out lastSelection.  Here is a copy of the override from the thread:

Ext.create('Ext.form.field.ComboBox', {
    ...
    allowBlank: true,
    forceSelection: true,
    beforeBlur: function(){
        var value = this.getRawValue();
        if(value == ''){
            this.lastSelection = [];
        }</span>
        this.doQueryTask.cancel();
       this.assertValue();
    }</span>
});

Or to apply the override to all combo boxes:

Ext.require("Ext.form.field.ComboBox", function () {
    Ext.override(Ext.form.field.ComboBox, {
        beforeBlur: function () {
            if (this.getRawValue().length === 0) {
                this.lastSelection = [];
            }
            this.callOverridden();
        }
    });
});

2. Add Clear Trigger

Another solution is to add a ‘clear’ trigger when the combobox has a value.  That solution is found at Combobox Extjs4 – Empty line.

3. ClearButton UX

On a related note, I really like the ClearButton plugin found at Ext.ux.form.field.ClearButton – Small clear button/icon over field.  I use this extensively in one of my applications.  It works well, except you can’t access the clear icon from the keyboard.

4. Empty Row

The other solution is to add an empty row to the data store backing the combobox, usually as the first record.

Version Info:
Ext JS 4.1.1

Ext JS 4: A Remote to Local QueryMode Hybrid ComboBox

I’ve needed this multiple times, an Ext JS combobox that is “remote”, but after loading all the combobox data on the first trigger click or when the user types in the combo field, it becomes “local”. Basically, I just don’t want the database hit unless the user actually uses the combobox.

Since this was my second or third time needing this, it’s time for a plugin. (gist 3659866)

And to use it, create the store for your combo box as having autoLoad set to false, and just put the plugin on the combobox.

// Define the model for a State
Ext.regModel('State', {
    fields: [
        {type: 'string', name: 'abbr'},
        {type: 'string', name: 'name'},
        {type: 'string', name: 'slogan'}
    ]
});

// The data store holding the states
var store = Ext.create('Ext.data.Store', {
    model: 'State',
	autoLoad: false,
	proxy: {
		type: 'ajax',
		url: '/states.json',
		reader: {
			type: 'json',
			root: 'states'
		}
	}
});

// Simple ComboBox using the data store
var simpleCombo = Ext.create('Ext.form.field.ComboBox', {
    fieldLabel: 'Select a single state',
    renderTo: 'simpleCombo',
    displayField: 'name',
    width: 500,
    labelWidth: 130,
    store: store,
    queryMode: 'remote',
    typeAhead: true,
    plugins: ['remotetolocalcombo']
});

Version Info: Ext JS 4.1.1

Ext JS 4: ComboBox and the Backspace Key

I have an Ext JS 4 ComboBox with the following config:

{
	xtype: 'combo',
	fieldLabel: 'My Combo',
	emptyText: 'Select one...',
	queryMode: 'local',
	editable: false,
	lastQuery: '',
	allowBlank: false,
	forceSelection: true,
	validateOnChange: false,
	validateOnBlur: false,
	enforceMaxLength: false,
}

The important config here is “editable: false”. It turns out that if this config is set and the combobox has focus and the backspace button is pressed, the browser (IE and Chrome) will handle this event, which happens to go back one in the browser history. Not really the user experience I was looking for.

Funny how even though I’d been through my form thousands of times, I never hit the backspace key on this field. All it took was one user…

To fix it, I add the following settings to the combobox configuration:

	enableKeyEvents: true,
	listeners: {
		keydown: function(obj, e) {
			if (e.getCharCode() == e.BACKSPACE) {
				e.preventDefault();
			}
		}
	}

This prevents the backspace key event from bubbling up to the browser.

This post discusses a different aspect of comboboxes, but it help me get to the “aha – enableKeyEvents” moment. Sencha ExtJS: Allow users to find without searching in a ComboBox