Weird bug preventing ExtJS checkboxes from submitting properly
October 24, 2008 by Ed Spencer · 1 Comment
This applies to ExtJS 2.2, the most current version as of the time of writing.
Checkboxes often make their way into my Ext JS forms. Sometimes, though, they don’t behave as expected. Checking and unchecking them would frequently fail, simply not doing anything. Sometimes it would work, sometimes it wouldn’t – how frustrating!
It turns out there is a bug with ticking/unticking checkboxes in Ext. If you click on the checkbox itself everything works fine – the image of the checkbox updates and the correct value is submitted. If however you click on the checkbox’s label, the image of the checkbox is updated but the correct value is not submitted. So if the box started off unticked and you ticked it by clicking the label, the image is updated but nothing else happens.
This is extremely unintuitive because you can see that the box has been checked, but its internal representation hasn’t actually changed. Because I usually click the label this took me over an hour to track down, so I hope this helps someone out. Once I had identified the bug, a quick Google search points to this thread on the ExtJS forums, which has some guidance on this.
Cleaning up an example Ext JS form
August 8, 2008 by Ed Spencer · 7 Comments
One of my recent Ext JS forms had a section which looked like this:
items: [
new Ext.Button({
text: 'Preview Video',
iconCls: 'play',
handler: function() {
var win;
if (!win) {
win = new Ext.Window({
title: 'Preview Video',
modal: true,
height: 377,
width: 368,
items: [
new Ext.Panel({
autoLoad: '/admin/videos/' + video_id + '/preview.html'
})
],
buttons: [
{
text: 'OK',
handler: function() {
win.close();
}
}
]
});
};
win.show();
}
})
]
Not horrific but not nice either – let’s DRY this up. It’s not too pleasant to read but all it’s really doing is rendering a customised Ext.Button which opens up a modal Ext.Window, in which is loaded the contents of a known url.
Ok so let’s start with that Window. First, we’ll make a subclass of Ext.Window:
/**
* AdFunded.views.Video.PreviewWindow
* @extends Ext.Window
* A simple Preview window for the given video_id
*/
AdFunded.views.Video.PreviewWindow = function(config) {
var config = config || {};
Ext.applyIf(config, {
title: 'Preview Video',
modal: true,
height: 377,
width: 368,
items: [
new Ext.Panel({
autoLoad: '/admin/videos/' + config.video_id + '/preview.html'
})
],
buttons: [
{
text: 'OK',
scope: this,
handler: function() {
this.window.close();
}
}
]
});
AdFunded.views.Video.PreviewWindow.superclass.constructor.call(this, config);
this.window = this;
};
Ext.extend(AdFunded.views.Video.PreviewWindow, Ext.Window);
Ext.reg('video_preview_window', AdFunded.views.Video.PreviewWindow);
Note the namespacing employed above – within an Ext MVC framework I have been developing across several projects for the last few months, all views follow this structure. AdFunded is the name of the application. The precise structure doesn’t matter here, but using a namespace for each app does.
So we’ve taken the Window setup out of our view now, which leaves us with:
items: [
new Ext.Button({
text: 'Preview Video',
iconCls: 'play',
handler: function() {
var win;
if (!win) {
win = new AdFunded.views.Video.PreviewWindow({video_id: id});
};
win.show();
}
})
]
Great – we’ve gone from 34 lines in our view to 15, and scored ourselves a reusable Window component which we can call from anywhere in the app. Nice work, but there’s more to come… If we’re going to use the Preview Window again, we’ll probably need to use that Preview Button again too. Let’s see:
/**
* AdFunded.views.Video.PreviewButton
* @extends Ext.Button
* Displays a Preview Window for the given video_id
*/
AdFunded.views.Video.PreviewButton = function(config) {
var config = config || {};
Ext.applyIf(config, {
text: 'Preview Video',
iconCls: 'play',
handler: function() {
var win = new AdFunded.views.Video.PreviewWindow({video_id: config.video_id});
win.show();
}
});
AdFunded.views.Video.PreviewButton.superclass.constructor.call(this, config);
};
Ext.extend(AdFunded.views.Video.PreviewButton, Ext.Button);
Ext.reg('video_preview_button', AdFunded.views.Video.PreviewButton);
Which leaves us with the following the the view:
items: [
{
xtype: 'video_preview_button',
video_id: id
}
]
We’ve now gone from 34 lines to 6 (in the view at least), but the point is not about cutting out lines of code – it’s creating reusable components. We’ve added 20 lines overall this way but we now have two extra components that we can call on at any time (with minimal lines of code), safe in the knowledge that they will provide a consistent experience each time.
ExtJS Radio Buttons and Square Brackets
August 8, 2008 by Ed Spencer · 6 Comments
While creating an ExtJS form with several radio buttons today I ran into a bug which caused none of them to work as expected, even though there were no errors/exceptions. To cut a long story short, it was because I was setting the name to “schedule[include_type]” – like this:
{
xtype: 'radio',
name: 'schedule[include_type]',
inputValue: 'page',
boxLabel: 'Show page:'
}
This radio button is one of 4, which allows the user which type of file they want to include on a particular model (a Schedule in this case) – be it Page, Video, Category or one other. The thing is – none of them work with the square brackets in the name. If you remove the brackets, they all work correctly, but the server-side is relying on those brackets to be present to group the data correctly.
In the end I bit the bullet and updated my submit method to add a new parameter directly – here’s a full example:
form = new Ext.form.FormPanel({
items: [
{
xtype: 'radio',
name: 'include_type',
inputValue: 'page',
boxLabel: 'Show page:'
},
{
xtype: 'radio',
name: 'include_type',
inputValue: 'category',
boxLabel: 'Show category:'
},
... plus some extra items
],
buttons: [
{
text: 'Save',
handler: function() {
//find the currently selected include_type from the form
var include_type = this.form.getValues()['include_type'];
//note the params option - this needs to be added manually otherwhise
//schedule[include_type] won't appear
form.form.submit({
waitMsg: 'Saving Data...',
params: "schedule[include_type]=" + include_type,
url: some url...
});
}
}
]
})
Note: I don’t usually add buttons in the way above so I’m not sure if the form.form.submit will work correctly here – see http://extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel for information about overriding submit.
So what we’re doing here is finding which radio button is currently checked, and appending this under “schedule[include_type]” when POSTing the form variables to the server. This really isn’t pleasant but seems to be the best way around this limitation for now.
I regularly use square brackets in other Ext JS Fields – Radio Buttons seem to be the only ones that have this problem. http://extjs.com/forum/showthread.php?p=185296 has a bit of background behind this, but no real solution.
Useful Rails javascript expansions for EXTJS
April 16, 2008 by Ed Spencer · 1 Comment
If you’re using Edge Rails (or > 2.1, which isn’t out at time of writing), and are using the EXT JS framework anywhere, here are a couple of handy javascript include tag expansions to clean up your views. Just chuck them into any file in your config/initializers directory:
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext => ['ext/adapter/ext/ext-base', 'ext/ext-all'] ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext_grid_filter => ['ext/ux/menu/EditableItem', 'ext/ux/menu/RangeMenu', 'ext/ux/grid/GridFilters', 'ext/ux/grid/filter/Filter', 'ext/ux/grid/filter/StringFilter', 'ext/ux/grid/filter/DateFilter', 'ext/ux/grid/filter/ListFilter', 'ext/ux/grid/filter/NumericFilter', 'ext/ux/grid/filter/BooleanFilter']
The top one includes the relevant EXT base files and the second one includes all the Grid Filters from the excellent Filter Grid plugin (see http://ccinct.com/lab/filter-grid/.
Include them as usual like this:
javascript_include_tag :ext, :ext_grid_filter, :cache => 'ext_javascripts'
EXT remote-loading forms with Combo boxes
April 16, 2008 by Ed Spencer · 5 Comments
Something that’s harder than it should be is populating an EXT edit form with form data, where one of the form fields is a select box. If there is a specific set of values that this select can take, then you can hard code that using a SimpleStore, like this:
var exampledata = [['AL', 'Alabama'],
['AK', 'Alaska'],
['AZ', 'Arizona'],
['AR', 'Arkansas'],
['CA', 'California']];
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state'],
data : exampleData
});
var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
valueField: 'abbr',
... etc ...
});
form = new Ext.form({
items: [combo],
... etc ...
});
form.load(url_to_load_from);
So that will populate the select box with the static values you’ve defined (the 5 states above), then when the form loads it will select the appropriate option automatically.
So far so good, but what if you need to load what goes into the select box dynamically? Well, first you’ll need to set up your remote data store (my server is sending back JSON data, hence the JsonReader):
store = new Ext.data.Store({
url: 'url_to_load_combo_data_from',
reader: new Ext.data.JsonReader(
{root: 'states',totalProperty: 'results'},
[
{name: 'name', type: 'string', mapping: 'state.name'},
{name: 'abbr', type: 'string', mapping: 'state.abbr'}
]
)
});
This will consume data like this:
{
"states": [
{"state": {"name": "Alabama", "abbr": "AL"}},
{"state": {"name": "Alaska", "abbr": "AK"}}
],
"results": "2"
}
And populate the store with a collection of state records which can then be loaded into the combobox.
Then all you need to do is load the store before loading the form data, and your comboboxes will be correctly populated, displaying the correct option. Here’s the full example:
store = new Ext.data.Store({
url: 'url_to_load_combo_data_from',
reader: new Ext.data.JsonReader(
{root: 'states',totalProperty: 'results'},
[
{name: 'name', type: 'string', mapping: 'state.name'},
{name: 'abbr', type: 'string', mapping: 'state.abbr'}
]
)
});
var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
valueField: 'abbr'
... etc ...
});
form = new Ext.formPanel({
items: [combo]
});
store.load();
form.load(your_form_data_url);
Be wary using the pagination options on the combobox here (see http://extjs.com/deploy/dev/docs/?class=Ext.form.ComboBox) – the reason being if your state’s ‘abbr’ features on the second page of the results it won’t populate the correct options into the combo box.