Ext JS 3.2 beta out today
March 9, 2010 by Ed Spencer · 5 Comments
We pushed out a beta release of Ext JS 3.2 this morning. Although we’ve marked it as beta, it’s a pretty solid release and we expect to release a final version shortly. The DataView transitions are especially fun – watch this space for a fuller example…
Here’s a quick rundown of the features we added:
- Multiple sorting and filtering on Stores
- Animated DataView transitions
- Composite Fields
- Multiple handles on Sliders
- Sliders in forms
- A couple of Toolbar plugins
- A new, more accessible theme
One of the big projects we’ve undertaken that most people probably won’t find so exciting is ramping up our internal QA efforts. Our unit test coverage has increased dramatically in the past couple of months, and we’ve built infrastructure to run all of our tests on every browser/OS we support in a fully automated fashion. Doing TDD on Ext JS is an awesome feeling.
I’ll talk more in the future about what we’re doing internally to ensure the quality of our code, framework performance and rendering.
2010: The year Ext JS takes over
January 13, 2010 by Ed Spencer · 18 Comments
On January 1st 2010 I officially joined Ext JS to take over the role of lead developer. After living and breathing Ext for the last 3 years I am delighted to have joined the company itself. Ext JS has lead the way in developing rich client side applications since the very first release; this is a tradition we will continue and build upon.
2010 is going to be an extremely exciting year for Ext JS. A new focus is being placed on helping developers create their applications much more quickly, with the help of advanced creation tools and a standardised application architecture right out of the box.
We will continue the performance improvements started in 3.1 to make sure that Ext applications really fly. Ext JS 3.2 will be the fastest, most stable version ever released.
2010 is also the year that Ext JS becomes much easier to learn. With a completely reinvented learning section, Ext will no longer take months to learn and understand – even our API documentation will get a facelift.
The upcoming Marketplace will be the perfect venue to find and share new, high quality components created by our awesome developer community. Think of the Marketplace as the App Store for Ext JS – full of great offerings that are easy to drop in to any application.
Calling all able-minded Ext JS developers
Ext JS is already the best JavaScript library in the world for creating rich, desktop-quality applications on the web. If you want to help us make it even better, I want to hear from you.
As well as creating new components and improving our application support, we need people to help us maintain the quality and stability of what we already have. If you’re intimate with Ext and think you have what it takes to get involved, drop me a PM and introduce yourself.
Ext.ux.Exporter – export any Grid to Excel or CSV
November 24, 2009 by Ed Spencer · 15 Comments
Sometimes we want to print things, like grids or trees. The Ext JS printing plugin is pretty good for that. But what if we want to export them instead? Enter Ext.ux.Exporter.
Ext.ux.Exporter allows any store-based component (such as grids) to be exported, locally, to Excel or any other format. It does not require any server side programming – the export document is generated on the fly, entirely in JavaScript.
The extension serves as a base for exporting any kind of data, but comes bundled with a .xls export formatter suitable for exporting any Grid straight to Excel. Here’s how to do that:
var grid = new Ext.grid.GridPanel({
store: someStore,
tbar : [
{
xtype: 'exportbutton',
store: someStore
}
],
//your normal grid config goes here
});
Clicking the Download button in the top toolbar iterates over the data in the store and creates an Excel file locally, before Base64 encoding it and redirecting the browser via a data url. If you have Excel or a similar program installed your browser should ask you to save the file or open it with Excel.
I put together a quick example of the plugin in action inside the repository, just clone or download the code and drag the examples/index.html file into your browser to run it.
The Exporter will work with any store or store-based component. It also allows export to any format – for example CSV or PDF. Although the Excel Formatter is probably the most useful, implementing a CSV or other Formatter should be trivial – check out the Excel Formatter example in the ExcelFormatter directory.
Making RowEditor use your column renderers
October 29, 2009 by Ed Spencer · 1 Comment
The RowEditor plugin is one of my favourite Ext JS components. It basically allows any row on a grid to be turned into an adhoc form on the fly, saving you the effort of defining additional form components.
Recently I had a grid which had a few fields that don’t have an editor, something like this:
var myGrid = new Ext.grid.GridPanel({
plugins: [new Ext.ux.grid.RowEditor()],
columns: [
{
header : "Username",
dataIndex: 'username',
editor : new Ext.form.TextField()
},
{
header : "Signup date",
dataIndex: 'created_at',
renderer : Ext.util.Format.dateRenderer('m/d/Y')
}
]
});
Simple stuff – we just show a username and a signup date, which is altered by a renderer. When we double-click a row it turns into an editable row, and we get a textfield allowing us to edit the username. Unfortunately, while in edit mode our date renderer is ignored, and the raw value displayed instead.
Thankfully, we can fix this by altering RowEditor’s source code. The method we need to change is startEditing, which sadly suffers from long method syndrome. About halfway into that method there’s a for loop, which we’re going to alter to look like this:
for (var i = 0, len = cm.getColumnCount(); i < len; i++){
val = this.preEditValue(record, cm.getDataIndex(i));
f = fields[i];
//our changes start here
var column = cm.getColumnById(cm.getColumnId(i));
val = column.renderer.call(column, val, {}, record);
//our changes end here
f.setValue(val);
this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
We didn’t really have to do much, just grab the renderer for the column and pass it the default value and the record which was found earlier in the method.
For the curious, the empty object we pass in as the second argument to the renderer is what would usually be the ‘meta’ object (see the renderer documentation on the Column class). Under the covers, RowEditor actually creates an Ext.form.DisplayField instance for each column that you don’t specify an editor for. This is why we use f.setValue(val); above. DisplayField doesn’t have the same meta stuff as a normal cell would, so if you’re looking to customise CSS via the metadata you’ll have to do something like this instead:
columns: [
{
...
editor: new Ext.form.DisplayField({
cls: 'myCustomCSSClass',
style: 'border: 10px solid red;'
})
}
]
Pretty easy. It’s a shame we have to overwrite the source code as this makes the solution less future proof, but if you look at RowEditor’s source code you’ll see why a 45 line override would be equally unpleasant.
Writing Better JavaScript – split up long methods
October 6, 2009 by Ed Spencer · Leave a Comment
For the second time this week I’m going to pick on the usually delightful Ext JS library. Last time we discussed the overzealous use of the Module pattern; this time it’s the turn of bloated methods.
As before, I’m not really picking on Ext at all – this happens all over the place. But again, this is the library closest to my heart and the one I know the best.
The Problem
We’re going to take a look at Ext.data.XmlReader’s readRecords method. Before we get started though, I’ll repeat that this is intended as an example of an approach, not a whine at Ext in particular.
/**
* Create a data block containing Ext.data.Records from an XML document.
* @param {Object} doc A parsed XML document.
* @return {Object} records A data block which is used by an {@link Ext.data.Store} as
* a cache of Ext.data.Records.
*/
readRecords: function(doc) {
/**
* After any data loads/reads, the raw XML Document is available for further custom processing.
* @type XMLDocument
*/
this.xmlData = doc;
var root = doc.documentElement || doc;
var q = Ext.DomQuery;
var recordType = this.recordType, fields = recordType.prototype.fields;
var sid = this.meta.idPath || this.meta.id;
var totalRecords = 0, success = true;
if(this.meta.totalRecords){
totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
}
if(this.meta.success){
var sv = q.selectValue(this.meta.success, root, true);
success = sv !== false && sv !== 'false';
}
var records = [];
var ns = q.select(this.meta.record, root);
for(var i = 0, len = ns.length; i < len; i++) {
var n = ns[i];
var values = {};
var id = sid ? q.selectValue(sid, n) : undefined;
for(var j = 0, jlen = fields.length; j < jlen; j++){
var f = fields.items[j];
var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue);
v = f.convert(v, n);
values[f.name] = v;
}
var record = new recordType(values, id);
record.node = n;
records[records.length] = record;
}
return {
success : success,
records : records,
totalRecords : totalRecords || records.length
};
}
Anyone care to tell me what this actually does? Personally, I have absolutely no idea. I recently found myself needing to implement an XmlReader subclass with a twist which required understanding how this works, and let’s just say it wasn’t easy!
So what is it that makes the above so terrifyingly hard to understand? Well, in no particular order:
- It’s too long – you’d need to be a genius to easily understand what’s going on here
- The variable names don’t make much sense – some of the oddest include ‘q’, ‘ns’, ‘v’, ‘f’ and ’sv’
- There’s minimal commenting – we’re given a single-line clue at the very top as to what these 40-odd lines do
A Solution
Let’s see how the reworked code below addresses each of the concerns above:
- Although we end up with more lines of code here, no single method is more than around 10 LOC
- No single letter variable names – you no longer have to decode what ’sv’ means
- Constructive commenting allows rapid comprehension by skimming the text
One additional and enormous benefit here comes directly from splitting logic into discrete methods. Previously if you’d wanted to implement your own logic to determine success, get the total number of records or even build a record from an XML node you’d be stuck. There was no way to selectively override that logic without redefining that entire monster method.
With our new approach this becomes trivial:
Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
readRecords: function(doc) {
this.xmlData = doc;
//get local references to frequently used variables
var root = doc.documentElement || doc,
records = [],
nodes = Ext.DomQuery.select(this.meta.record, root);
//build an Ext.data.Record instance for each node
Ext.each(nodes, function(node) {
records.push(this.buildRecordForNode(node));
}, this);
return {
records : records,
success : this.wasSuccessful(root),
totalRecords: this.getTotalRecords(root) || records.length
};
},
/**
* Returns a new Ext.data.Record instance using data from a given XML node
* @param {Element} node The XML node to extract Record values from
* @return {Ext.data.Record} The record instance
*/
buildRecordForNode: function(node) {
var domQuery = Ext.DomQuery,
idPath = this.meta.idPath || this.meta.id,
id = idPath ? domQuery.selectValue(idPath, node) : undefined;
var record = new this.recordType({}, id);
record.node = node;
//iterate over each field in our record, find it in the XML node and convert it
record.fields.each(function(field) {
var mapping = Ext.value(field.mapping, field.name, true),
rawValue = domQuery.selectValue(mapping, node, field.defaultValue),
value = field.convert(rawValue, node);
record.set(field.name, value);
});
return record;
},
/**
* Returns the total number of records indicated by the server response
* @param {XMLDocument} root The XML response root node
* @return {Number} total records
*/
getTotalRecords: function(root) {
var metaTotal = this.meta.totalRecords;
return metaTotal == undefined
? 0
: Ext.DomQuery.selectNumber(metaTotal, root, 0);
},
/**
* Returns true if the response document includes the expected success property
* @param {XMLDocument} root The XML document root node
* @return {Boolean} True if the XML response was successful
*/
wasSuccessful: function(root) {
var metaSuccess = this.meta.success;
//return true for any response except 'false'
if (metaSuccess == undefined) {
return true;
} else {
var successValue = Ext.DomQuery.selectValue(metaSuccess, root, true);
return successValue !== false && successValue !== 'false';
}
}
});
(For brevity I have omitted the existing readRecords comment blocks from the above)
I suggest that you structure your code in this way at least 99% of the time. The one exception is if high performance is an issue. If you are in a situation where every millisecond counts (you probably aren’t), then taking the former route becomes more acceptable (though there’s still no excuse for not adding a few comments explaining what the code actually does).
My refactored code almost certainly runs slower than the original as it doesn’t take as much advantage of cached local variables as the monolithic version does. For library-level code this can make sense if the performance gain is significant, but for the everyday code you and I write it is rarely a good idea.
I’ll be watching.