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
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