PDA

View Full Version : Ext.grid.RowNumberer replacement



dongryphon
4 Dec 2010, 5:17 PM
Greetings,

I needed to number rows in my grids and in some cases those grids were paged and others they used drag-n-drop to reorder items (http://www.sencha.com/forum/showthread.php?21913). There have been many posts with various improvements to the over-simplified Ext.grid.RowNumberer, but here's mine.

It handles:


Dynamic changes to the store (without refreshing the grid view)
Paging of the store
Reconfiguring the grid's store
Dynamically calculating the column width based on the max row number
Pretty formatting of the row number (using Ext.util.Format.numberRenderer)

Usage:



function makeGrid () {
var rowNum = new Ext.ux.DynamicRowNumberer();
return {
xtype: "grid",
plugins: [rowNum],
columns: [rowNum, ... ]
};
}
The code:


Ext.ux.DynamicRowNumberer = Ext.extend(Object, function () {

var base;

function formatNone (num) {
return num;
}

// produces a number with the widest digit of the same string length
function getWorstCaseNumber (num) {
for (var n = 8, ret = n; ret < num; ) {
ret = ret * 10 + n;
}
return ret;
}

return {
isColumn: true,

dataIndex: "",
fixed: true,
header: "",
hideable: false,
id: "numberer",
menuDisabled: true,
rowspan: undefined,
sortable: false,
width: 16,

/**
The format used for the row number. A value of true uses "0,000", whereas false
performs no special formatting. If this is a string, the string is passed to
Ext.util.Format.numberRenderer to produce a proper formatter. Otherwise, this
property must be a function that formats its single number argument.
*/
format: false,

/**
Determines whether or not the column width is adjusted dynamically. The width
is calculated based on the number of records in the store.
*/
manageWidth: true,

/**
Additional cell padding to apply when managing the column width.
*/
padding: 5,

_baseIndex: 0,
_refreshRowIndex: Number.MAX_VALUE,

constructor: function (config) {
base = Ext.ux.DynamicRowNumberer.superclass;
base.constructor.call(this, config);

Ext.apply(this, config);
this.renderer = this.renderer.createDelegate(this);

if (this.format === false) {
this.format = formatNone;
} else if (this.format === true) {
this.format = Ext.util.Format.numberRenderer("0,000");
} else if (typeof(this.format) === "string") {
this.format = Ext.util.Format.numberRenderer(this.format);
}
// else format must be a configured function
},

init: function (grid) {
this.grid = grid;
grid.on({
afterrender: this.onGridAfterRender,
reconfigure: this.onGridReconfigure,
scope: this
});
this.bindStore(grid.store);
},

bindStore: function (store) {
if (this.store) {
this.store.un("add", this.onStoreAdd, this);
this.store.un("load", this.onStoreLoad, this);
this.store.un("remove", this.onStoreRemove, this);
}
if (store) {
store.on({
add: this.onStoreAdd,
load: this.onStoreLoad,
remove: this.onStoreRemove,
scope: this
});
}
this.store = store;
},

destroy: function () {
this.bindStore(null);
if (this.refreshTimer) {
window.clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
},

onGridAfterRender: function () {
this.refreshSoon();
},

onGridReconfigure: function () {
this.bindStore(this.grid.store);
},

onStoreAdd: function (store, records, index) {
this.refreshSoon(index);
},

onStoreLoad: function () {
var st = this.store, o = st.lastOptions, lp = o && o.params;
var pn = st.paramNames || st.defaultParamNames, sp = lp && lp[pn.start];

if (lp && !isNaN(sp)) {
this._baseIndex = sp;
}
this.refreshSoon();
},

onStoreRemove: function (store, record, index) {
this.refreshSoon(index);
},

refreshNow: function () {
this.refreshTimer = null;

var g = this.grid, cm = g.getColumnModel(), col = cm.getIndexById(this.id);
var st = this.store, rows = g.view.getRows(), cls = ".x-grid3-col-" + this.id;

for (var i = this._refreshRowIndex, n = st.getCount(); i < n; ++i) {
var r = rows[i];
var el = Ext.fly(r).select(cls).first();
var t = this.renderer(i, null, st.getAt(i), i, col, st);
el.dom.innerHTML = t;
}
delete this._refreshRowIndex;

this.updateColumnWidth();
},

refreshSoon: function (index) {
if (!this.grid.rendered) {
return;
}
if (typeof(index) === "number") {
this._refreshRowIndex = Math.min(this._refreshRowIndex, index);
}
if (!this.refreshTimer) {
this.refreshTimer = this.refreshNow.defer(10, this);
}
},

renderer: function (v, p, record, rowIndex, colIndex, store) {
if (p && this.rowspan) {
p.cellAttr = 'rowspan="'+this.rowspan+'"';
}

var n = this._baseIndex + rowIndex + 1;
var ret = this.format(n);
return ret;
},

updateColumnWidth: function () {
if (this.manageWidth) {
var st = this.store, n = this._baseIndex + st.getCount();
var wc = getWorstCaseNumber(n);

if (wc === this._widthBasis) {
return;
}

var g = this.grid, cm = g.getColumnModel(), col = cm.getIndexById(this.id);
var el = g.view.mainBody.select(".x-grid3-col-"+this.id).first();

if (!el) {
return;
}

this._widthBasis = wc;
var s = this.format(wc);
var w = Ext.util.TextMetrics.measure(el, s).width;

var ep = el.getPadding("lr"), pp = el.parent().getPadding("lr");
w += ep + pp + this.padding;

cm.setColumnWidth(col, w);
}
}
};

}());

History:
10-Dec-2010: Fixed bug with stores loading when not rendered.
8-Dec-2010: Fixed bug with initial store binding.
7-Dec-2010: Removed toLocaleString (it includes 2 decimal digits in IE)
Please feel free to suggest further improvements. I want this to be the last word on the subject of grid row numbers! :)

Enjoy!
Don

rpnoble
7 Dec 2010, 9:30 PM
Liked your plugin, but a few issues. My datastore is 800 records long and you maganged width does not fully expand to handle the 3 digit width. Also it was very slow in rendering.

dongryphon
8 Dec 2010, 9:38 AM
I tested up to 5 digit row numbers with and without formatting in FF and IE8 and the width worked for me. What browser did you test with?

dongryphon
8 Dec 2010, 9:41 AM
Also, what was the performance difference w/ and w/o the row numberer? 800 rows is always a challenge for a grid :)

Are you using Ext.ux.grid.BufferView? I haven't had a chance to test with that UX yet, but it is important for rendering large numbers of rows.

Oh, and thanks testing the plugin and for the feedback!

dongryphon
13 Dec 2010, 10:56 PM
I've done some more testing and haven't noticed any performance issues induced by the RowNumberer. I've used the Firebug profiler and nothing in the top 20 is related to RowNumberer. That is what I would expect because the only thing it does typically is render cells which is a function call or two, depending on formatting or not. The measurement of text is done once using the max row number (basically).

Any info you can provide on your performance issue would be helpful.

Thanks!

Best,
Don