Developing an Ext JS 4 Checkbox Group with “Other (please specify)” option

I’m currently working on a dynamic forms/workflow solution for one of our clients. One of the requirements is to allow the user to not only choose from a set of values that are represented as a series of checkboxes, but also be able to specify their own custom value if necessary.

Image

In order to meet this requirement I subclassed the Ext.form.CheckboxGroup and it’s related layout – Ext.layout.container.CheckboxGroup

Defining a custom layout

Making room for the additional checkbox was a relatively straightforward affair. We just added an additional row to the layout class’ render template as illustrated on line 15 of the following source listing :

Ext.define('Ext.ux.layout.container.CheckboxOther', {
    extend: 'Ext.layout.container.CheckboxGroup',
    alias: ['layout.checkboxgroupother'],

    renderTpl: [
        '<table id="{ownerId}-innerCt" class="' + Ext.plainTableCls + '" cellpadding="0"',
            'role="presentation" style="{tableStyle}">',
            '<tbody role="presentation"><tr role="presentation">',
            '<tpl for="columns">',
                '<td class="{parent.colCls}" valign="top" style="{style}" role="presentation">',
                    '{% this.renderColumn(out,parent,xindex-1) %}',
                '</td>',
            '</tpl>',
        '</tr>',
        '<tr><td colspan="{[values.columnCount]}" class="{colCls}" valign="top" style="{style}" role="presentation"></td></tr>',
        '</tbody>',
        '</table>'
    ]

});

Extending the Ext.form.CheckboxGroup class

The final step was to extend the Ext.form.CheckboxGroup class in order to inject the “Other” textfield. This involved:

  • Rendering the textfield into the layout template (line 34)
  • Using the Ext.util.TextMetrics class to determine the pixel width of the text field prompt and then setting its labelWidth property accordingly (lines 30-31)
  • Overriding the getValue(), setValue(), getSubmitData(), and getModelData() fields to handle the textfield input.
Ext.define('Ext.ux.form.CheckboxGroupOther', {
	extend: 'Ext.form.CheckboxGroup',
	alias: 'widget.checkboxgroupother',
	requires: ['Ext.ux.layout.container.CheckboxGroupOther'],
	layout: 'checkboxgroupother',

	config: {
		otherBoxLabel: 'Other :',
		otherName: ''
	},

	maxSelections: 0,
	minSelections: 0,

	readOnly: false,

	// private

	textfield: null,

	initComponent: function() {

		// prevent checkboxes from submitting
		if (this.items) {
			for (var i = 0; i < this.items.length; i++) {
				this.items[i].submitValue = false;
			}
		}
		this.callParent(arguments);
	},

	afterRender: function() {

		this.callParent(arguments);
		var tm = Ext.create('Ext.util.TextMetrics');
		var labelWidth = tm.getWidth(this.getOtherBoxLabel() + 5);
		var colspanEl = Ext.get(this.getEl().select('td [colspan=' + this.columns + ']').elements[0]);
		var textfieldType = "textfield";

		if (this.readOnly) {
			textfieldType = "displayfield";
		}

		this.textfield = Ext.widget(textfieldType, {
			renderTo: colspanEl,
			labelWidth: labelWidth,
			margin: '10 0 0 0',
			width: '100%',
			fieldLabel: this.getOtherBoxLabel(),
			style: {
				'table-layout': 'auto !important'
			},
			name: this.getOtherName(),
			listeners: {
				change: {
					fn: this.otherBoxChange,
					scope: this
				}
			}
		});

	},

	isDirty: function() {

		if (this.textfield.isDirty()) {
			return true;
		} else {
			return this.callParent(arguments);
		}
	},

	setReadOnly: function(readOnly) {

		this.textfield.setReadOnly(readOnly);
		this.callParent(arguments);

	},

	reset: function() {
		this.textfield.reset();
		this.callParent(arguments);
	},

	otherBoxChange: function(cmp) {
		this.validate();
	},

	resetOriginalValue: function() {
		this.textfield.resetOriginalValue();
		this.callParent(arguments);
	},

	getErrors: function() {

		var errors = [];
		var checkedBoxes = this.getChecked();
		if (!this.allowBlank && Ext.isEmpty(checkedBoxes) && Ext.isEmpty(this.textfield.getValue())) {
			errors.push(this.blankText);
		}

		var numSelections = checkedBoxes.length;

		if (!Ext.isEmpty(this.textfield.getValue())) {
			numSelections++;
		}

		if (numSelections < this.minSelections) {
			errors.push("You must make a minimum of " + this.minSelections + " selections");
		}

		if (this.maxSelections > 0 && numSelections > this.maxSelections) {
			errors.push("You can make a maximum of " + this.maxSelections + " selections");
		}

		return errors;
	},


	// setValue syntax:
	//
	// setValue({rb: ['1','2', '3', 'Other: Foo']})
	//
	//

	setValue: function(value) {

		var cb = null;

		if (!Ext.isArray(value)) {
			value = [value];
		}

		for (var i = 0; i < this.items.items.length; i++) {

			cb = this.items.items[i];

			if (Ext.Array.contains(value, cb.inputValue)) {
				cb.setValue(true);
			} else {
				cb.setValue(false);
			}

		}

		for (var i = 0; i < value.length; i++) {
			if (Ext.isString(value[i]) && value[i].indexOf('Other: ') == 0) {
				this.textfield.setValue(value[i].substr(7));
			} else {
				this.textfield.setValue('');
			}
		}

	},


	getValue: function() {

		var values = [];

		for (var i = 0; i < this.items.items.length; i++) {
			var cb = this.items.items[i];

			if (cb.isCheckbox && cb.getValue()) {
				values.push(cb.inputValue)
			}
		}

		if (this.textfield) {
			var tfValue = this.textfield.getValue();
			if (!Ext.isEmpty(tfValue)) {
				values.push('Other: ' + tfValue);
			}
		}

		/*
		if (values.length == 1) {
			values = values[0];
		}
		*/


		return values;
	},


	getSubmitData: function() {

		var val = new Object();
		val[this.name] = this.getValue();

		return val;

	},

	getModelData: function() {

		var val = new Object();
		val[this.name] = this.getValue();

		return val;
	}



});

What are you waiting for?

3 thoughts on “Developing an Ext JS 4 Checkbox Group with “Other (please specify)” option

  1. Dave

    Hello, excellent component! however when I try to use it I get the following error thrown when getting colspanEl in afterRender: Uncaught SyntaxError: Failed to execute ‘querySelectorAll’ on ‘Element’: ‘td [colspan=2]’ is not a valid selector. Im using it like so in a form: {
    xtype: ‘checkboxgroupother’,
    defaults: {
    name: ‘othertest’,
    xtype: ‘checkbox’
    },
    columns: 2,
    allowBlank: false,
    itemId: ‘othertest’,
    items: [
    {
    boxLabel: ‘One’,
    inputValue: ‘0’
    },
    {
    boxLabel: ‘Two’,
    inputValue: ‘1’
    }
    ]
    },

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s