Monthly Archives: March 2014

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?

Integrating Ext JS 4 and the TinyMCE 4 Rich Text WYSIWYG Editor

TinyMCE is generally considered to be one of the best WYSIWYG browser-based editors that’s currently available. Not only does it have a ton of native features, but it also has an extensible plugin architecture that enables developers to add additional functionality with relative. One of it’s best features is the ability for a user to expand the editor to full-screen, thereby enabling a much better user experience. It also has a copy/paste plugin that can filter out nasty extraneous Microsoft Word markup.

TinyMCE is open-source and has LGPL and Commercial licenses available.

Image

Oleg Schildt was gracious enough to develop an Ext extension for TinyMCE 4, which we improved by adding the following features:

  • Making the class a lot more developer-friendly by adding config attributes to load plugins and automatically instantiate associated buttons
  • Added ICE version control plugin support (depicted above)
  • Automatically load TinyMCE from the CDN if not present
  • Added autoFocus param to prevent automatic focus on instantiation
  • Added dynamic show/hide of header & footer on focus / blur

We also created a custom a Sencha Architect user extension to support it, as well as bundled all of the source code into an Ext JS 4 “package” for easy deployment to your Sencha Cmd-based apps.

A few geeky technical points:

In order for a Sencha Architect user extension to operate properly, the Ext class must not be reliant on external javascripts. Therefore, we threw a little kludge into our codebase that “tricks” Architect into thinking that we’re instantiating a simple textarea:


// architect design canvas runs pages with
// a url starting with "ionp"

if (location.href.indexOf('ionp') == 0) {  

    // kludge for sencha architect canvas

    Ext.define('Ext.ux.form.TinyMceTextArea', {
        extend: 'Ext.form.field.TextArea',
        alias: 'widget.tinymce'
    });

} else {

    // actually load the class

    Ext.define('Ext.ux.form.TinyMceTextArea', {

        extend: 'Ext.form.field.TextArea',
        alias: 'widget.tinymce'

        // define the "guts" of the class here
    });
}

In order to make the component more “developer-friendly”, we used Ext.loader.loadScript() to load the TinyMCE and ice plugin scripts automatically. Note that ICE requires jQuery (for now):

if (!window['tinymce']) {
    Ext.Loader.loadScript({
        url: '//tinymce.cachefly.net/4.0/tinymce.min.js',
        onLoad: function() {
            if (window.loadIce) {
                Ext.Loader.loadScript({
                    url: '//code.jquery.com/jquery-1.9.1.min.js',
                    onLoad: function() {
                        Ext.Loader.loadScript({
                            url: '//code.jquery.com/jquery-migrate-1.0.0.js'
                        });
                    }
                });
                Ext.Loader.loadScript({
                    url: 'packages/TinyMCE/resources/ice/ice-master.min.js'
                });
                Ext.Loader.loadScript({
                    url: 'packages/TinyMCE/resources/tinymce/plugins/ice/plugin.js'
                });
            }
        },
        scope: this
    });
}

And finally, we packaged everything (including the Sencha Architect plugin) into a Sencha package, which enables you to combine your component’s source code, related design assets, and (optionally) Sencha Architect plugin into neatly organized folder structure. Note that in the following screenshot, you can configure the TinyMCE editor plugins and other attributes by filling out a simple configuration form.

Sencha Architect Integration

 

So what are you waiting for?

9 Chrome Plugins to help you debug your web apps and GSD

I’m personally obsessed with GSD (getting stuff done) and find the following (free) developer tools to be invaluable in this regard. Check ’em out, and if you have any other suggestions, please list them in the comments!

POSTMAN

Postman is a free Google Chrome App that enables you to easily test your REST API’s.

Image

Features include:

  • Ability to create requests quickly
  • Simulate HTTP requests with file upload support
  • Auto-Formatting of JSON and XML responses
  • Basic an OAuth Helpers
  • Share collections between developers
  • Create unit tests by adding “Jetpacks” ($9.95 US)

CLEAR CACHE

Clear Cache is a free Google Chrome App that enables you to reliably clear your browser cache at the click of a button.

Image

APACHE RIPPLE

Apache Ripple™ is a web based mobile environment simulator designed to enable rapid development of mobile web applications for various web application frameworks, such as Apache Cordova™ and BlackBerry® WebWorks™. It can be paired with current web based mobile development workflows to decrease time spent developing and testing on real devices and/or simulators.

Image

It is free software, licensed under the Apache License, Version 2.0.

Features include:

  • Disabling cross-domain security restrictions
  • Accelerometer simulation
  • Geolocation simulation

JSON EDITOR ONLINE

JSON Editor is a Chrome plugin for viewing, editing, and formatting JSON. It shows your data in an editable treeview and in a code editor.

JSONEditor

Features include:

  • View and edit JSON side by side in treeview and a code editor.
  • Edit, add, move, remove, and duplicate fields and values.
  • Change type of values.
  • Sort arrays and objects.
  • Colorized values, color depends of the value type.
  • Search & highlight text in the treeview.
  • Undo and redo all actions.
  • Load and save files and urls.
  • Format, compact, and inspect JSON in the code editor.

REGEXP TESTER

This free Chrome plugin enables you to quickly and easily verify the validity of your javascript regular expressions.

regexp

ICOMOON

Icomoon enables you to rapidly select and encode icons as a custom font. This is particularly useful  if you’re developing custom themes for Sencha Touch and Ext JS 4.21+ applications.

icomoon

SPEEDTRACER

Speed Tracer by Google is a tool to help you identify and fix performance problems in your web applications. It visualizes metrics that are taken from low level instrumentation points inside of the browser and analyzes them as your application runs.

speedtracer

If you’re obsessed with Javascript RIA application performance, check it out!

CHROMEVOX

If you want to verify that your Javascript-based RIA (Ext JS 4) is accessible for the vision impaired, check out this free Chrome plugin.

APP INSPECTOR FOR SENCHA

App Inspector for Sencha™ is an extension for Google Chrome DevTools which makes debugging Sencha Ext JS and Touch applications easier than ever before!

App Inspector for Sencha™ helps you to inspect your component tree, data stores, events, and layouts. It provides similar functionality to Illuminations for Developers.

appinspector