PDA

View Full Version : Need to support Groupping view and Buffered View together Help



vasa
4 Aug 2009, 3:21 AM
since data is very large in my grid ~ 5000 rows grid performance was very slow.
Pagination cannot be implemented as restrictions on pagination in our project.
i used Buffered view it works fine but i need groupingview also but dont know
how to club it together.(live grid not a solution for me now).
please tell me how to combine buffer view and grouping view together.

thanks

saJoshua
4 Aug 2009, 3:56 AM
It is not advisable to display so many records because of browser performance. The main reason being that the rendering overhead of so many rows "hangs" most peoples browsers. It comes down to basic HTML being rendered. That has nothing to do with ExtJS and is a simple browser limitation. I know this doesn't solve your issue.

It looks like you'll need to consolidate the Ext.grid.GroupingView and the Ext.ux.grid.BufferView, but there are so many overlapping methods (eg initTemplates, etc). It looks like a lot of work and I'm really not sure if it's even possible.

vasa
4 Aug 2009, 4:34 AM
But do we any solutions now to use Ext.grid.GroupingView and Ext.ux.grid.BufferView
together. A example will be better.That will help me a lot

vasa
10 Aug 2009, 5:46 AM
So are there any one supported buffer view and grouping view together.
I am literally waiting if extjs team could do something about the needfull.

Condor
10 Aug 2009, 5:55 AM
You would have to create a GridView that combines the code from both GroupingView and BufferView.

This, however, would be an extremely difficult task, because BufferView completely relies on every row being the same height (uninterrupted by group headers).

vasa
10 Aug 2009, 6:02 AM
Is extjs team will provide the needed feature in upcoming versions??

Condor
10 Aug 2009, 6:06 AM
Almost certainly not. It's just to difficult to create without writing way to much code for a library.

SeaMonkey
19 Nov 2009, 3:02 AM
Hello, I need a component Grid that has Multigrouping view and buffer view together.

Someone could make it, even if I need to pay it?

Thanks

harimyself_b
24 Nov 2009, 5:54 AM
SeaMonkey,

I hope this can solve your problem. It may not render on/off records as accurately as simple BufferingView. But, I'm sure that it can satisfy your requirement.


Ext.ux.BufferedGroupingView = Ext.extend(Ext.grid.GroupingView, {

//disable group headers collapsable option.
interceptMouse : Ext.emptyFn,

rowHeight: 19,
borderHeight: 2,
scrollDelay: 100,
cacheSize: 20,
cleanDelay: 500,
groupsList:[],
passedGroupHeadersHeight:0,
lastSavedVisibleRowsTemp:null,

initTemplates : function(){
Ext.ux.BufferedGroupingView.superclass.initTemplates.call(this);
var ts = this.templates;
// empty div to act as a place holder for a row
ts.rowHolder = new Ext.Template(
'<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
);
ts.rowHolder.disableFormats = true;
ts.rowHolder.compile();

ts.rowBody = new Ext.Template(
'<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<tbody><tr>{cells}</tr>',
(this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
'</tbody></table>'
);
ts.rowBody.disableFormats = true;
ts.rowBody.compile();
},

getStyleRowHeight : function(){
return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
},

syncScroll: function(){
Ext.ux.BufferedGroupingView.superclass.syncScroll.apply(this, arguments);
this.update();
},

// a (optionally) buffered method to update contents of gridview
update: function(){
if (this.scrollDelay) {
if (!this.renderTask) {
this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
}
this.renderTask.delay(this.scrollDelay);
}else{
this.doUpdate();
}
},

doUpdate: function(){
if (this.getVisibleRowCount() > 0) {
var g = this.grid, cm = g.colModel, ds = g.store;
var cs = this.getColumnData();

var vr = this.getVisibleRows();
for (var i = vr.first; i <= vr.last; i++) {
// if row is NOT rendered and is visible, render it
if(!this.isRowRendered(i)){
var html = this.doBufferViewRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
this.getRow(i).innerHTML = html;
}
}
this.clean();
}
},

// a buffered method to clean rows
clean : function(){
if(!this.cleanTask){
this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
}
this.cleanTask.delay(this.cleanDelay);
},

doClean: function(){
if (this.getVisibleRowCount() > 0) {
var vr = this.getVisibleRows();
vr.first -= this.cacheSize;
vr.last += this.cacheSize;

var i = 0, rows = this.getRows();
// if first is less than 0, all rows have been rendered
// so lets clean the end...
if(vr.first <= 0){
i = vr.last + 1;
}
for(var len = this.ds.getCount(); i < len; i++){
// if current row is outside of first and last and
// has content, update the innerHTML to nothing
if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
rows[i].innerHTML = '';
}
}
}
},

layout: function(){
Ext.ux.BufferedGroupingView.superclass.layout.call(this);
this.update();
},

// private
doRender : function(cs, rs, ds, startRow, colCount, stripe){
if(rs.length < 1){
return '';
}
var groupField = this.getGroupField(),
colIndex = this.cm.findColumnIndex(groupField),
g;

this.enableGrouping = !!groupField;

if(!this.enableGrouping || this.isUpdating){
return this.doBufferViewRender.apply(
this, arguments,false);
}

var gstyle = 'width:'+this.getTotalWidth()+';';

var gidPrefix = this.grid.getGridEl().id;
var cfg = this.cm.config[colIndex];
var groupRenderer = cfg.groupRenderer || cfg.renderer;
var prefix = this.showGroupName ?
(cfg.groupName || cfg.header)+': ' : '';

var groups = [], curGroup, i, len, gid;
for(i = 0, len = rs.length; i < len; i++){
var rowIndex = startRow + i,
r = rs[i],
gvalue = r.data[groupField];

g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
if(!curGroup || curGroup.group != g){
gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
// if state is defined use it, however state is in terms of expanded
// so negate it, otherwise use the default.
var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
curGroup = {
group: g,
gvalue: gvalue,
text: prefix + g,
groupId: gid,
startRow: rowIndex,
rs: [r],
cls: gcls,
style: gstyle
};
groups.push(curGroup);
}else{
curGroup.rs.push(r);
}
r._groupId = gid;
}

var buf = [];
for(i = 0, len = groups.length; i < len; i++){
// maintain groups in an array.
this.groupsList[i] = groups[i];

g = groups[i];
this.doGroupStart(buf, g, cs, ds, colCount);
buf[buf.length] = this.doBufferViewRender(
cs, g.rs, ds, g.startRow, colCount, stripe, false);

this.doGroupEnd(buf, g, cs, ds, colCount);
}
groups = null;
return buf.join('');
},

getVisisbleGroupHeadersHeight : function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
for(var i=0; i<this.groupsList.length; i++) {
var header = Ext.get(this.groupsList[i].groupId);
if(header != null){
var headerOffsetY = header.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = header.dom.firstChild.offsetHeight;
if(headerOffsetY < 0){
this.passedGroupHeadersHeight += headerHeight;
}
if(headerOffsetY >= 0 && headerOffsetY < visibleHeight) {
if((headerOffsetY+headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight-headerOffsetY);
}
else if(headerOffsetY < 0 && (headerOffsetY+headerHeight) > 0)
totalGroupHeadersHeight += (headerOffsetY+headerHeight);
}
}
return totalGroupHeadersHeight;
},

getCalculatedRowHeight : function(){
return this.rowHeight + this.borderHeight;
},

getVisibleRowCount : function(){
var rh = this.getCalculatedRowHeight();
var visibleHeight = this.scroller.dom.clientHeight;
//already used height.
var uh = this.getVisisbleGroupHeadersHeight(visibleHeight);
//alert(Math.ceil((visibleHeight-uh) / rh));
return (visibleHeight < 1) ? 0 : Math.ceil((visibleHeight-uh) / rh);
},

getVisibleRows: function(){
var st = this.scroller.dom.scrollTop;
if(this.lastSavedVisibleRowsTemp != null &&
st == this.lastSavedVisibleRowsTemp.scroll &&
this.lastSavedVisibleRowsTemp.rows.first >= 0) {
if(this.lastSavedVisibleRowsTemp.rows.last-this.lastSavedVisibleRowsTemp.rows.last)
return this.lastSavedVisibleRowsTemp.rows;
}
var count = this.getVisibleRowCount();
var sc = st - this.passedGroupHeadersHeight;
this.passedGroupHeadersHeight = 0;
// start 4 rows before to aviod empty space on top caused by random miscalculations.
var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-4);
this.lastSavedVisibleRowsTemp =
{
scroll: st,
rows: {
first: Math.max(start, 0),
last: Math.min(start + count + 3, this.ds.getCount()-1)
}
};
return this.lastSavedVisibleRowsTemp.rows;
},

isRowRendered: function(index){
var row = this.getRow(index);
return row && row.childNodes.length > 0;
},

doBufferViewRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
var rh = this.getStyleRowHeight();
var vr = this.getVisibleRows();
var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
// buffers
var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
for (var j = 0, len = rs.length; j < len; j++) {
r = rs[j]; cb = [];
var rowIndex = (j+startRow);
var visible = rowIndex >= vr.first && rowIndex <= vr.last;
if (visible) {
for (var i = 0; i < colCount; i++) {
c = cs[i];
p.id = c.id;
p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
p.attr = p.cellAttr = "";
p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
p.style = c.style;
if (p.value == undefined || p.value === "") {
p.value = "†";
}
// BHM doesn't show dirty flag.
//if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
// p.css += ' x-grid3-dirty-cell';
//}
cb[cb.length] = ct.apply(p);
}
}
var alt = [];
if(stripe && ((rowIndex+1) % 2 == 0)){
alt[0] = "x-grid3-row-alt";
}
if(r.dirty){
alt[1] = " x-grid3-dirty-row";
}
rp.cols = colCount;
if(this.getRowClass){
alt[2] = this.getRowClass(r, rowIndex, rp, ds);
}
rp.alt = alt.join(" ");
rp.cells = cb.join("");
buf[buf.length] = !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
}
return buf.join('');
},

// this function is over-loaded to improve the performance.
getRow : function(row){
if(!this.enableGrouping){
var rs = Ext.grid.GroupingView.superclass.getRows.call(this);
return rs[row];
}
var g, gs = this.getGroups();
for(var i = 0, len = gs.length; i < len; i++){
g = gs[i].childNodes[1].childNodes;
if(g.length <= row) {
row -= g.length;
continue;
}
else
if(row >= 0)
return g[row];
}
},

// private
processRows : function(startRow, skipStripe){
if(!this.ds || this.ds.getCount() < 1){
return;
}
var rows = this.getRows();
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
var F=" x-grid3-row-alt ";

Ext.each(rows, function(row, idx){
row.rowIndex = idx;
if(!skipStripe){
var A=((idx + 1)%2==0);
var G=(" "+row.className+" ").indexOf(F)!=-1;

if(A && A!=G){
row.className+=" x-grid3-row-alt"
}
else if(A!=G){
row.className=row.className.replace("x-grid3-row-alt","")
}
}
});
// add first/last-row classes
if(startRow === 0){
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
},

/**
* This method is re-written to support both buffering
* and Grouping.
*/
updateAllColumnWidths:function(){
var D=this.getTotalWidth();
var H=this.cm.getColumnCount();
var F=[];
for(var B=0;B<H;B++){
F[B]=this.getColumnWidth(B);
}
this.innerHd.firstChild.firstChild.style.width=D;
for(var B=0;B<H;B++){
var C=this.getHeaderCell(B);
C.style.width=F[B]
}
var G=this.getRows();
for(var B=0,E=G.length;B<E;B++){
G[B].style.width=D;

// following condition must be verified for buffering grid.
if(G[B].hasChildNodes()) {
G[B].firstChild.style.width=D;
var I=G[B].firstChild.rows[0];
for(var A=0;A<H;A++){
I.childNodes[A].style.width=F[A]
}
}
}
this.onAllColumnWidthsUpdated(F,D)
},

onColumnWidthsUpdated : function(cols, w, tw){
// template method
},

setFocusRowCell : function (focusRowCell) {
this.focusRowCell = focusRowCell;
}
});

emily
26 Nov 2009, 3:38 AM
Hi harimyself_b,

I am potentially interested in the extension you just posted (although my needs are a little more complicated), so thanks for sharing :)

Anyway, I was just reading through the code, and I noticed a comments saying '// this function is over-loaded to improve the performance.', refering to the getRow method. I was just wandering what is meant by that? I have come across overloading in Java (where you have different functions that have the same name, but with different domains and ranges), but have never heard of doing it with Javascript, and wondering if it meant something different in this context?

The problem I keep coming up against is that I wish to use something like the bufferView or even like LiveGrid with on demand fetching or rendering of data, but these only currently work with rows of a fixed height (for obvious reasons), and I would ideally like to be able to use them with things like rowexpander / grouping (clearly I am asking a lot!).

It is hard to make a ui when dealing with very large datasets where the user needs to be able to freely investigate things within the data (without necessarily knowing what they are looking for). It would be good to find a solution in extjs for these problems.

Em

harimyself_b
26 Nov 2009, 5:21 AM
Hi Emily, i think you are little confused with term "over loading", its actually "over-riding". Yes it was over-ridden to improve performance especially in IE. Yeah, you are right.. if ExtJS community can come up with features like you suggested It'll heaven for both developers and users aswell.
It is very tricky to do row-expansion especially when you are working with 'Dynamic Row Rendering Grids' like Buffered and Live grids. I hope ExtJS will come up with solution.

emily
26 Nov 2009, 6:11 AM
Hi Emily, i think you are little confused with term "over loading", its actually "over-riding". Yes it was over-ridden to improve performance especially in IE. Yeah, you are right.. if ExtJS community can come up with features like you suggested It'll heaven for both developers and users aswell.
It is very tricky to do row-expansion especially when you are working with 'Dynamic Row Rendering Grids' like Buffered and Live grids. I hope ExtJS will come up with solution.

Ah - that makes sense! I wasn't confusing the terms, the the comment says the wrong thing (it says 'over-loaded' when it should have said 'over-ridden'). Thanks for clearing that up :-)

I think this problem can be solved without using silly amounts of maths, and I think I have an idea, but it isn't very cooked yet. I will have a play and a think to see if it will work, but I also need to look at how the existing ones are done at the moment (I haven't really delved yet!).

Em

bulforce
3 Feb 2010, 12:50 PM
BufferView should be standard feature of the grid component!!!

Why do we need to render rows that are not visible!

Come on guys get yourself together and make it happen, ext cannot be enterprise platform that is lacking such a crucial functionality.

The ultimate user experience is coming with the free scrolling not with pagination and etc.

radtad
14 Oct 2010, 4:36 PM
harimyself_b,

I love the work! It's works great... with one exception. If I filter the underlying store, the rows do not get populated correctly. Basically there's no data in some of the rows. Any idea how to fix the problem?

rivarecords
28 Dec 2010, 8:59 AM
SeaMonkey,

I hope this can solve your problem. It may not render on/off records as accurately as simple BufferingView. But, I'm sure that it can satisfy your requirement.


Ext.ux.BufferedGroupingView = Ext.extend(Ext.grid.GroupingView, {

//disable group headers collapsable option.
interceptMouse : Ext.emptyFn,

rowHeight: 19,
borderHeight: 2,
scrollDelay: 100,
cacheSize: 20,
cleanDelay: 500,
groupsList:[],
passedGroupHeadersHeight:0,
lastSavedVisibleRowsTemp:null,

initTemplates : function(){
Ext.ux.BufferedGroupingView.superclass.initTemplates.call(this);
var ts = this.templates;
// empty div to act as a place holder for a row
ts.rowHolder = new Ext.Template(
'<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
);
ts.rowHolder.disableFormats = true;
ts.rowHolder.compile();

ts.rowBody = new Ext.Template(
'<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<tbody><tr>{cells}</tr>',
(this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
'</tbody></table>'
);
ts.rowBody.disableFormats = true;
ts.rowBody.compile();
},

getStyleRowHeight : function(){
return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
},

syncScroll: function(){
Ext.ux.BufferedGroupingView.superclass.syncScroll.apply(this, arguments);
this.update();
},

// a (optionally) buffered method to update contents of gridview
update: function(){
if (this.scrollDelay) {
if (!this.renderTask) {
this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
}
this.renderTask.delay(this.scrollDelay);
}else{
this.doUpdate();
}
},

doUpdate: function(){
if (this.getVisibleRowCount() > 0) {
var g = this.grid, cm = g.colModel, ds = g.store;
var cs = this.getColumnData();

var vr = this.getVisibleRows();
for (var i = vr.first; i <= vr.last; i++) {
// if row is NOT rendered and is visible, render it
if(!this.isRowRendered(i)){
var html = this.doBufferViewRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
this.getRow(i).innerHTML = html;
}
}
this.clean();
}
},

// a buffered method to clean rows
clean : function(){
if(!this.cleanTask){
this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
}
this.cleanTask.delay(this.cleanDelay);
},

doClean: function(){
if (this.getVisibleRowCount() > 0) {
var vr = this.getVisibleRows();
vr.first -= this.cacheSize;
vr.last += this.cacheSize;

var i = 0, rows = this.getRows();
// if first is less than 0, all rows have been rendered
// so lets clean the end...
if(vr.first <= 0){
i = vr.last + 1;
}
for(var len = this.ds.getCount(); i < len; i++){
// if current row is outside of first and last and
// has content, update the innerHTML to nothing
if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
rows[i].innerHTML = '';
}
}
}
},

layout: function(){
Ext.ux.BufferedGroupingView.superclass.layout.call(this);
this.update();
},

// private
doRender : function(cs, rs, ds, startRow, colCount, stripe){
if(rs.length < 1){
return '';
}
var groupField = this.getGroupField(),
colIndex = this.cm.findColumnIndex(groupField),
g;

this.enableGrouping = !!groupField;

if(!this.enableGrouping || this.isUpdating){
return this.doBufferViewRender.apply(
this, arguments,false);
}

var gstyle = 'width:'+this.getTotalWidth()+';';

var gidPrefix = this.grid.getGridEl().id;
var cfg = this.cm.config[colIndex];
var groupRenderer = cfg.groupRenderer || cfg.renderer;
var prefix = this.showGroupName ?
(cfg.groupName || cfg.header)+': ' : '';

var groups = [], curGroup, i, len, gid;
for(i = 0, len = rs.length; i < len; i++){
var rowIndex = startRow + i,
r = rs[i],
gvalue = r.data[groupField];

g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
if(!curGroup || curGroup.group != g){
gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
// if state is defined use it, however state is in terms of expanded
// so negate it, otherwise use the default.
var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
curGroup = {
group: g,
gvalue: gvalue,
text: prefix + g,
groupId: gid,
startRow: rowIndex,
rs: [r],
cls: gcls,
style: gstyle
};
groups.push(curGroup);
}else{
curGroup.rs.push(r);
}
r._groupId = gid;
}

var buf = [];
for(i = 0, len = groups.length; i < len; i++){
// maintain groups in an array.
this.groupsList[i] = groups[i];

g = groups[i];
this.doGroupStart(buf, g, cs, ds, colCount);
buf[buf.length] = this.doBufferViewRender(
cs, g.rs, ds, g.startRow, colCount, stripe, false);

this.doGroupEnd(buf, g, cs, ds, colCount);
}
groups = null;
return buf.join('');
},

getVisisbleGroupHeadersHeight : function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
for(var i=0; i<this.groupsList.length; i++) {
var header = Ext.get(this.groupsList[i].groupId);
if(header != null){
var headerOffsetY = header.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = header.dom.firstChild.offsetHeight;
if(headerOffsetY < 0){
this.passedGroupHeadersHeight += headerHeight;
}
if(headerOffsetY >= 0 && headerOffsetY < visibleHeight) {
if((headerOffsetY+headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight-headerOffsetY);
}
else if(headerOffsetY < 0 && (headerOffsetY+headerHeight) > 0)
totalGroupHeadersHeight += (headerOffsetY+headerHeight);
}
}
return totalGroupHeadersHeight;
},

getCalculatedRowHeight : function(){
return this.rowHeight + this.borderHeight;
},

getVisibleRowCount : function(){
var rh = this.getCalculatedRowHeight();
var visibleHeight = this.scroller.dom.clientHeight;
//already used height.
var uh = this.getVisisbleGroupHeadersHeight(visibleHeight);
//alert(Math.ceil((visibleHeight-uh) / rh));
return (visibleHeight < 1) ? 0 : Math.ceil((visibleHeight-uh) / rh);
},

getVisibleRows: function(){
var st = this.scroller.dom.scrollTop;
if(this.lastSavedVisibleRowsTemp != null &&
st == this.lastSavedVisibleRowsTemp.scroll &&
this.lastSavedVisibleRowsTemp.rows.first >= 0) {
if(this.lastSavedVisibleRowsTemp.rows.last-this.lastSavedVisibleRowsTemp.rows.last)
return this.lastSavedVisibleRowsTemp.rows;
}
var count = this.getVisibleRowCount();
var sc = st - this.passedGroupHeadersHeight;
this.passedGroupHeadersHeight = 0;
// start 4 rows before to aviod empty space on top caused by random miscalculations.
var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-4);
this.lastSavedVisibleRowsTemp =
{
scroll: st,
rows: {
first: Math.max(start, 0),
last: Math.min(start + count + 3, this.ds.getCount()-1)
}
};
return this.lastSavedVisibleRowsTemp.rows;
},

isRowRendered: function(index){
var row = this.getRow(index);
return row && row.childNodes.length > 0;
},

doBufferViewRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
var rh = this.getStyleRowHeight();
var vr = this.getVisibleRows();
var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
// buffers
var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
for (var j = 0, len = rs.length; j < len; j++) {
r = rs[j]; cb = [];
var rowIndex = (j+startRow);
var visible = rowIndex >= vr.first && rowIndex <= vr.last;
if (visible) {
for (var i = 0; i < colCount; i++) {
c = cs[i];
p.id = c.id;
p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
p.attr = p.cellAttr = "";
p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
p.style = c.style;
if (p.value == undefined || p.value === "") {
p.value = " ";
}
// BHM doesn't show dirty flag.
//if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
// p.css += ' x-grid3-dirty-cell';
//}
cb[cb.length] = ct.apply(p);
}
}
var alt = [];
if(stripe && ((rowIndex+1) % 2 == 0)){
alt[0] = "x-grid3-row-alt";
}
if(r.dirty){
alt[1] = " x-grid3-dirty-row";
}
rp.cols = colCount;
if(this.getRowClass){
alt[2] = this.getRowClass(r, rowIndex, rp, ds);
}
rp.alt = alt.join(" ");
rp.cells = cb.join("");
buf[buf.length] = !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
}
return buf.join('');
},

// this function is over-loaded to improve the performance.
getRow : function(row){
if(!this.enableGrouping){
var rs = Ext.grid.GroupingView.superclass.getRows.call(this);
return rs[row];
}
var g, gs = this.getGroups();
for(var i = 0, len = gs.length; i < len; i++){
g = gs[i].childNodes[1].childNodes;
if(g.length <= row) {
row -= g.length;
continue;
}
else
if(row >= 0)
return g[row];
}
},

// private
processRows : function(startRow, skipStripe){
if(!this.ds || this.ds.getCount() < 1){
return;
}
var rows = this.getRows();
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
var F=" x-grid3-row-alt ";

Ext.each(rows, function(row, idx){
row.rowIndex = idx;
if(!skipStripe){
var A=((idx + 1)%2==0);
var G=(" "+row.className+" ").indexOf(F)!=-1;

if(A && A!=G){
row.className+=" x-grid3-row-alt"
}
else if(A!=G){
row.className=row.className.replace("x-grid3-row-alt","")
}
}
});
// add first/last-row classes
if(startRow === 0){
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
},

/**
* This method is re-written to support both buffering
* and Grouping.
*/
updateAllColumnWidths:function(){
var D=this.getTotalWidth();
var H=this.cm.getColumnCount();
var F=[];
for(var B=0;B<H;B++){
F[B]=this.getColumnWidth(B);
}
this.innerHd.firstChild.firstChild.style.width=D;
for(var B=0;B<H;B++){
var C=this.getHeaderCell(B);
C.style.width=F[B]
}
var G=this.getRows();
for(var B=0,E=G.length;B<E;B++){
G[B].style.width=D;

// following condition must be verified for buffering grid.
if(G[B].hasChildNodes()) {
G[B].firstChild.style.width=D;
var I=G[B].firstChild.rows[0];
for(var A=0;A<H;A++){
I.childNodes[A].style.width=F[A]
}
}
}
this.onAllColumnWidthsUpdated(F,D)
},

onColumnWidthsUpdated : function(cols, w, tw){
// template method
},

setFocusRowCell : function (focusRowCell) {
this.focusRowCell = focusRowCell;
}
});


Is anyone using this extention or expanded on it? The issue I'm having is when all groups are collapsed, expanding the last group displays the correct number of rows but no data shows. If the group above it is expanded, data for the last group appears.


edit: Hi, this is my first post. Thanks in advance!

Eric24
28 Dec 2010, 9:33 AM
BufferView should be standard feature of the grid component!!!

Why do we need to render rows that are not visible!

Come on guys get yourself together and make it happen, ext cannot be enterprise platform that is lacking such a crucial functionality.

The ultimate user experience is coming with the free scrolling not with pagination and etc.

+1

rivarecords
3 Jan 2011, 10:54 AM
So the issue with the BufferedGroupingView was that if groups were collapsed, there was no way to determine the correct first and last visible rows as defined in the getVisibleRows method and the rows would appear without data.
First I tried modifying the getVisibleRows using the groupsList array because it captures the start row index for each group. This allowed me to expand any group and see data but still, if a group was collapsed between expanded groups, that group and any groups below would not display data. Also, this turned into a lot of code and made scrolling a little slow and weird.
The solution I came up with was to override the group click and toggle all groups then scroll to the y position of the group that was clicked.


if(name == 'mousedown' && e.button == 0){
//this.toggleGroup(hd.parentNode);
this.toggleAllGroups();
var gel = Ext.fly(hd.parentNode);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;
this.scroller.dom.scrollTop = headerOffsetY-headerHeight;
this.doUpdate();
}

It works well enough that I can live with it and the client is happy because I did away with paging and their grid is much faster to render data.
Here is the whole thing as written by harimyself_b with my modification.

Ext.ux.BufferedGroupingView = Ext.extend(Ext.grid.GroupingView, {
//disable group headers collapsable option.
// interceptMouse : Ext.emptyFn,
rowHeight: 19,
borderHeight: 2,
scrollDelay: 100,
cacheSize: 20,
cleanDelay: 500,
groupsList:[],
passedGroupHeadersHeight:0,
lastSavedVisibleRowsTemp:null,
initTemplates : function(){
Ext.ux.BufferedGroupingView.superclass.initTemplates.call(this);
var ts = this.templates;
// empty div to act as a place holder for a row
ts.rowHolder = new Ext.Template(
'<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
);
ts.rowHolder.disableFormats = true;
ts.rowHolder.compile();
ts.rowBody = new Ext.Template(
'<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<tbody><tr>{cells}</tr>',
(this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
'</tbody></table>'
);
ts.rowBody.disableFormats = true;
ts.rowBody.compile();
},
getStyleRowHeight : function(){
return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
},
syncScroll: function(){
Ext.ux.BufferedGroupingView.superclass.syncScroll.apply(this, arguments);
this.update();
},
// a (optionally) buffered method to update contents of gridview
update: function(){
if (this.scrollDelay) {
if (!this.renderTask) {
this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
}
this.renderTask.delay(this.scrollDelay);
}else{
this.doUpdate();
}
},
doUpdate: function(){
if (this.getVisibleRowCount() > 0) {
var g = this.grid, cm = g.colModel, ds = g.store;
var cs = this.getColumnData();
var vr = this.getVisibleRows();

for (var i = vr.first; i <= vr.last; i++) {
// if row is NOT rendered and is visible, render it
if(!this.isRowRendered(i)){
var html = this.doBufferViewRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
if(this.getRow(i)){
this.getRow(i).innerHTML = html;}
}
}
this.clean();
}
},
// a buffered method to clean rows
clean : function(){
if(!this.cleanTask){
this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
}
this.cleanTask.delay(this.cleanDelay);
},
doClean: function(){
if (this.getVisibleRowCount() > 0) {
var vr = this.getVisibleRows();
vr.first -= this.cacheSize;
vr.last += this.cacheSize;
var i = 0, rows = this.getRows();
// if first is less than 0, all rows have been rendered
// so lets clean the end...
if(vr.first <= 0){
i = vr.last + 1;
}
for(var len = this.ds.getCount(); i < len; i++){
// if current row is outside of first and last and
// has content, update the innerHTML to nothing
if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
rows[i].innerHTML = '';
}
}
}
},
layout: function(){
Ext.ux.BufferedGroupingView.superclass.layout.call(this);
this.update();
},
// private
doRender : function(cs, rs, ds, startRow, colCount, stripe){
if(rs.length < 1){
return '';
}
var groupField = this.getGroupField(),
colIndex = this.cm.findColumnIndex(groupField),
g;
this.enableGrouping = !!groupField;
if(!this.enableGrouping || this.isUpdating){
return this.doBufferViewRender.apply(
this, arguments,false);
}
var gstyle = 'width:'+this.getTotalWidth()+';';
var gidPrefix = this.grid.getGridEl().id;
var cfg = this.cm.config[colIndex];
var groupRenderer = cfg.groupRenderer || cfg.renderer;
var prefix = this.showGroupName ?
(cfg.groupName || cfg.header)+': ' : '';
var groups = [], curGroup, i, len, gid;
for(i = 0, len = rs.length; i < len; i++){
var rowIndex = startRow + i,
r = rs[i],
gvalue = r.data[groupField];

g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
if(!curGroup || curGroup.group != g){
gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
// if state is defined use it, however state is in terms of expanded
// so negate it, otherwise use the default.
var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';

//alert(gid);
curGroup = {
group: g,
gvalue: gvalue,
text: prefix + g,
groupId: gid,
startRow: rowIndex,
rs: [r],
cls: gcls,
style: gstyle
};
groups.push(curGroup);
}else{
curGroup.rs.push(r);
}
r._groupId = gid;
}
var buf = [];
for(i = 0, len = groups.length; i < len; i++){
// maintain groups in an array.
this.groupsList[i] = groups[i];
g = groups[i];
this.doGroupStart(buf, g, cs, ds, colCount);
buf[buf.length] = this.doBufferViewRender(
cs, g.rs, ds, g.startRow, colCount, stripe, false);
this.doGroupEnd(buf, g, cs, ds, colCount);
}
groups = null;

return buf.join('');
},
getVisisbleGroupHeadersHeight : function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
for(var i=0; i<this.groupsList.length; i++) {
var header = Ext.get(this.groupsList[i].groupId);
if(header != null){
var headerOffsetY = header.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = header.dom.firstChild.offsetHeight;
if(headerOffsetY < 0){
this.passedGroupHeadersHeight += headerHeight;
}
if(headerOffsetY >= 0 && headerOffsetY < visibleHeight) {
if((headerOffsetY+headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight-headerOffsetY);
}
else if(headerOffsetY < 0 && (headerOffsetY+headerHeight) > 0)
totalGroupHeadersHeight += (headerOffsetY+headerHeight);
}
}
return totalGroupHeadersHeight;
},
getCalculatedRowHeight : function(){
return this.rowHeight + this.borderHeight;
},
getVisibleRowCount : function(){
var rh = this.getCalculatedRowHeight();
var visibleHeight = this.scroller.dom.clientHeight;
//already used height.
var uh = this.getVisisbleGroupHeadersHeight(visibleHeight);
//alert(Math.ceil((visibleHeight-uh) / rh));
return (visibleHeight < 1) ? 0 : Math.ceil((visibleHeight-uh) / rh);
},
getVisibleRows: function(){

var st = this.scroller.dom.scrollTop;
if(this.lastSavedVisibleRowsTemp != null &&
st == this.lastSavedVisibleRowsTemp.scroll &&
this.lastSavedVisibleRowsTemp.rows.first >= 0) {
if(this.lastSavedVisibleRowsTemp.rows.last-this.lastSavedVisibleRowsTemp.rows.last)
return this.lastSavedVisibleRowsTemp.rows;
}
var count = this.getVisibleRowCount();
var sc = st - this.passedGroupHeadersHeight;
// start 4 rows before to aviod empty space on top caused by random miscalculations.
var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-8);
this.passedGroupHeadersHeight = 0;
this.lastSavedVisibleRowsTemp =
{
scroll: st,
rows: {
first: Math.max(start, 0),
last: Math.min(start + count + 4, this.ds.getCount()-1)
}
};
return this.lastSavedVisibleRowsTemp.rows;
},
isRowRendered: function(index){
var row = this.getRow(index);
return row && row.childNodes.length > 0;
},
doBufferViewRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
var rh = this.getStyleRowHeight();
var vr = this.getVisibleRows();
var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
// buffers
var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
for (var j = 0, len = rs.length; j < len; j++) {
r = rs[j]; cb = [];
var rowIndex = (j+startRow);
var visible = rowIndex >= vr.first && rowIndex <= vr.last;
if (visible) {
for (var i = 0; i < colCount; i++) {
c = cs[i];
p.id = c.id;
p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
p.attr = p.cellAttr = "";
p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
p.style = c.style;
if (p.value == undefined || p.value === "") {
p.value = " ";
}
// BHM doesn't show dirty flag.
//if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
// p.css += ' x-grid3-dirty-cell';
//}
cb[cb.length] = ct.apply(p);
}
}
var alt = [];
if(stripe && ((rowIndex+1) % 2 == 0)){
alt[0] = "x-grid3-row-alt";
}
if(r.dirty){
alt[1] = " x-grid3-dirty-row";
}
rp.cols = colCount;
if(this.getRowClass){
alt[2] = this.getRowClass(r, rowIndex, rp, ds);
}
rp.alt = alt.join(" ");
rp.cells = cb.join("");
buf[buf.length] = !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
}
return buf.join('');
},

// this function is over-loaded to improve the performance.
getRow : function(row){
if(!this.enableGrouping){
var rs = Ext.grid.GroupingView.superclass.getRows.call(this);
return rs[row];
}
var g, gs = this.getGroups();
for(var i = 0, len = gs.length; i < len; i++){
g = gs[i].childNodes[1].childNodes;
if(g.length <= row) {
row -= g.length;
continue;
}
else
if(row >= 0)
return g[row];
}
},
// private
processRows : function(startRow, skipStripe){
if(!this.ds || this.ds.getCount() < 1){
return;
}
var rows = this.getRows();
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
var F=" x-grid3-row-alt ";
Ext.each(rows, function(row, idx){
row.rowIndex = idx;
if(!skipStripe){
var A=((idx + 1)%2==0);
var G=(" "+row.className+" ").indexOf(F)!=-1;

if(A && A!=G){
row.className+=" x-grid3-row-alt"
}
else if(A!=G){
row.className=row.className.replace("x-grid3-row-alt","")
}
}
});
// add first/last-row classes
if(startRow === 0){
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
},
/**
* This method is re-written to support both buffering
* and Grouping.
*/
updateAllColumnWidths:function(){
var D=this.getTotalWidth();
var H=this.cm.getColumnCount();
var F=[];
for(var B=0;B<H;B++){
F[B]=this.getColumnWidth(B);
}
this.innerHd.firstChild.firstChild.style.width=D;
for(var B=0;B<H;B++){
var C=this.getHeaderCell(B);
C.style.width=F[B]
}
var G=this.getRows();
for(var B=0,E=G.length;B<E;B++){
G[B].style.width=D;
// following condition must be verified for buffering grid.
if(G[B].hasChildNodes()) {
G[B].firstChild.style.width=D;
var I=G[B].firstChild.rows[0];
for(var A=0;A<H;A++){
I.childNodes[A].style.width=F[A]
}
}
}
this.onAllColumnWidthsUpdated(F,D)
},
onColumnWidthsUpdated : function(cols, w, tw){
// template method
},
setFocusRowCell : function (focusRowCell) {
this.focusRowCell = focusRowCell;
},

processEvent: function(name, e){
Ext.grid.GroupingView.superclass.processEvent.call(this, name, e);
var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
if(hd){
// group value is at the end of the string
var field = this.getGroupField(),
prefix = this.getPrefix(field),
groupValue = hd.id.substring(prefix.length),
emptyRe = new RegExp('gp-' + Ext.escapeRe(field) + '--hd');

// remove trailing '-hd'
groupValue = groupValue.substr(0, groupValue.length - 3);

// also need to check for empty groups
if(groupValue || emptyRe.test(hd.id)){
this.grid.fireEvent('group' + name, this.grid, field, groupValue, e);
}

if(name == 'mousedown' && e.button == 0){
//this.toggleGroup(hd.parentNode);
this.toggleAllGroups();
var gel = Ext.fly(hd.parentNode);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;
this.scroller.dom.scrollTop = headerOffsetY-headerHeight;
this.doUpdate();

}
}
}

});

rivarecords
7 Jan 2011, 7:30 AM
I found that the getVisisbleGroupHeadersHeight wasn't returning anything resulting in blank rows when scrolling on large record sets.
So I made a few more tweeks...
Here's the latest BufferedGroupingView:

Ext.ux.BufferedGroupingView = Ext.extend(Ext.grid.GroupingView, {
rowHeight: 19,
borderHeight: 2,
scrollDelay: 100,
cacheSize: 20,
cleanDelay: 500,
groupsList:[],
passedGroupHeadersHeight:0,
lastSavedVisibleRowsTemp:null,
initTemplates : function(){
Ext.ux.BufferedGroupingView.superclass.initTemplates.call(this);
var ts = this.templates;
// empty div to act as a place holder for a row
ts.rowHolder = new Ext.Template(
'<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
);
ts.rowHolder.disableFormats = true;
ts.rowHolder.compile();
ts.rowBody = new Ext.Template(
'<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<tbody><tr>{cells}</tr>',
(this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
'</tbody></table>'
);
ts.rowBody.disableFormats = true;
ts.rowBody.compile();
},
getStyleRowHeight : function(){
return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
},
syncScroll: function(){
Ext.ux.BufferedGroupingView.superclass.syncScroll.apply(this, arguments);
this.update();
},
// a (optionally) buffered method to update contents of gridview
update: function(){
if (this.scrollDelay) {
if (!this.renderTask) {
this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
}
this.renderTask.delay(this.scrollDelay);
}else{
this.doUpdate();
}
},
doUpdate: function(){
if (this.getVisibleRowCount() > 0) {
var g = this.grid, cm = g.colModel, ds = g.store;
var cs = this.getColumnData();
var vr = this.getVisibleRows();

for (var i = vr.first; i <= vr.last; i++) {
// if row is NOT rendered and is visible, render it
if(!this.isRowRendered(i)){
var html = this.doBufferViewRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
if(this.getRow(i)){
this.getRow(i).innerHTML = html;}
}
}
this.clean();
}
},
// a buffered method to clean rows
clean : function(){
if(!this.cleanTask){
this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
}
this.cleanTask.delay(this.cleanDelay);
},
doClean: function(){
if (this.getVisibleRowCount() > 0) {
var vr = this.getVisibleRows();
vr.first -= this.cacheSize;
vr.last += this.cacheSize;
var i = 0, rows = this.getRows();
// if first is less than 0, all rows have been rendered
// so lets clean the end...
if(vr.first <= 0){
i = vr.last + 1;
}
for(var len = this.ds.getCount(); i < len; i++){
// if current row is outside of first and last and
// has content, update the innerHTML to nothing
if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
rows[i].innerHTML = '';
}
}
}
},
layout: function(){
Ext.ux.BufferedGroupingView.superclass.layout.call(this);
this.update();
},
// private
doRender : function(cs, rs, ds, startRow, colCount, stripe){
if(rs.length < 1){
return '';
}
var groupField = this.getGroupField(),
colIndex = this.cm.findColumnIndex(groupField),
g;
this.enableGrouping = !!groupField;
if(!this.enableGrouping || this.isUpdating){
return this.doBufferViewRender.apply(
this, arguments,false);
}
var gstyle = 'width:'+this.getTotalWidth()+';';
var gidPrefix = this.grid.getGridEl().id;
var cfg = this.cm.config[colIndex];
var groupRenderer = cfg.groupRenderer || cfg.renderer;
var prefix = this.showGroupName ?
(cfg.groupName || cfg.header)+': ' : '';
var groups = [], curGroup, i, len, gid;
for(i = 0, len = rs.length; i < len; i++){
var rowIndex = startRow + i,
r = rs[i],
gvalue = r.data[groupField];

g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
if(!curGroup || curGroup.group != g){
gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
// if state is defined use it, however state is in terms of expanded
// so negate it, otherwise use the default.
var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';

//alert(gid);
curGroup = {
group: g,
gvalue: gvalue,
text: prefix + g,
groupId: gid,
startRow: rowIndex,
rs: [r],
cls: gcls,
style: gstyle
};
groups.push(curGroup);
}else{
curGroup.rs.push(r);
}
r._groupId = gid;
}
var buf = [];
for(i = 0, len = groups.length; i < len; i++){
// maintain groups in an array.
this.groupsList[i] = groups[i];
g = groups[i];
this.doGroupStart(buf, g, cs, ds, colCount);
buf[buf.length] = this.doBufferViewRender(
cs, g.rs, ds, g.startRow, colCount, stripe, false);
this.doGroupEnd(buf, g, cs, ds, colCount);
}
groups = null;

return buf.join('');
},
getVisisbleGroupHeadersHeight : function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
var groups = this.getGroups();
for(var i = 0, len = groups.length; i < len; i++){
var gel = Ext.fly(groups[i]);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;

if(headerOffsetY < 0){
this.passedGroupHeadersHeight += headerHeight;
}
if(headerOffsetY >= 0 && headerOffsetY < visibleHeight) {
if((headerOffsetY+headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight-headerOffsetY);
}
else if(headerOffsetY < 0 && (headerOffsetY+headerHeight) > 0)
totalGroupHeadersHeight += (headerOffsetY+headerHeight);

}

return totalGroupHeadersHeight;
},
getCalculatedRowHeight : function(){
return this.rowHeight + this.borderHeight;
},
getVisibleRowCount : function(){
var rh = this.getCalculatedRowHeight();
var visibleHeight = this.scroller.dom.clientHeight;
//already used height.
var uh = this.getVisisbleGroupHeadersHeight(visibleHeight);
//alert(Math.ceil((visibleHeight-uh) / rh));
return (visibleHeight < 1) ? 0 : Math.ceil((visibleHeight-uh) / rh);
},
getVisibleRows: function(){

var st = this.scroller.dom.scrollTop;
if(this.lastSavedVisibleRowsTemp != null &&
st == this.lastSavedVisibleRowsTemp.scroll &&
this.lastSavedVisibleRowsTemp.rows.first >= 0) {
if(this.lastSavedVisibleRowsTemp.rows.last-this.lastSavedVisibleRowsTemp.rows.last)
return this.lastSavedVisibleRowsTemp.rows;
}
var count = this.getVisibleRowCount();
var sc = st - this.passedGroupHeadersHeight;
// start 4 rows before to aviod empty space on top caused by random miscalculations.
var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-4);
this.passedGroupHeadersHeight = 0;
this.lastSavedVisibleRowsTemp =
{
scroll: st,
rows: {
first: Math.max(start, 0),
last: Math.min(start + count + 4, this.ds.getCount()-1)
}
};
return this.lastSavedVisibleRowsTemp.rows;
},
isRowRendered: function(index){
var row = this.getRow(index);
return row && row.childNodes.length > 0;
},
doBufferViewRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
var rh = this.getStyleRowHeight();
var vr = this.getVisibleRows();
var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
// buffers
var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
for (var j = 0, len = rs.length; j < len; j++) {
r = rs[j]; cb = [];
var rowIndex = (j+startRow);
var visible = rowIndex >= vr.first && rowIndex <= vr.last;
if (visible) {
for (var i = 0; i < colCount; i++) {
c = cs[i];
p.id = c.id;
p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
p.attr = p.cellAttr = "";
p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
p.style = c.style;
if (p.value == undefined || p.value === "") {
p.value = " ";
}
// BHM doesn't show dirty flag.
//if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
// p.css += ' x-grid3-dirty-cell';
//}
cb[cb.length] = ct.apply(p);
}
}
var alt = [];
if(stripe && ((rowIndex+1) % 2 == 0)){
alt[0] = "x-grid3-row-alt";
}
if(r.dirty){
alt[1] = " x-grid3-dirty-row";
}
rp.cols = colCount;
if(this.getRowClass){
alt[2] = this.getRowClass(r, rowIndex, rp, ds);
}
rp.alt = alt.join(" ");
rp.cells = cb.join("");
buf[buf.length] = !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
}
return buf.join('');
},

// this function is over-loaded to improve the performance.
getRow : function(row){
if(!this.enableGrouping){
var rs = Ext.grid.GroupingView.superclass.getRows.call(this);
return rs[row];
}
var g, gs = this.getGroups();
for(var i = 0, len = gs.length; i < len; i++){
g = gs[i].childNodes[1].childNodes;
if(g.length <= row) {
row -= g.length;
continue;
}
else
if(row >= 0)
return g[row];
}
},
// private
processRows : function(startRow, skipStripe){
if(!this.ds || this.ds.getCount() < 1){
return;
}
var rows = this.getRows();
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
var F=" x-grid3-row-alt ";
Ext.each(rows, function(row, idx){
row.rowIndex = idx;
if(!skipStripe){
var A=((idx + 1)%2==0);
var G=(" "+row.className+" ").indexOf(F)!=-1;

if(A && A!=G){
row.className+=" x-grid3-row-alt"
}
else if(A!=G){
row.className=row.className.replace("x-grid3-row-alt","")
}
}
});
// add first/last-row classes
if(startRow === 0){
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
},
/**
* This method is re-written to support both buffering
* and Grouping.
*/
updateAllColumnWidths:function(){
var D=this.getTotalWidth();
var H=this.cm.getColumnCount();
var F=[];
for(var B=0;B<H;B++){
F[B]=this.getColumnWidth(B);
}
this.innerHd.firstChild.firstChild.style.width=D;
for(var B=0;B<H;B++){
var C=this.getHeaderCell(B);
C.style.width=F[B]
}
var G=this.getRows();
for(var B=0,E=G.length;B<E;B++){
G[B].style.width=D;
// following condition must be verified for buffering grid.
if(G[B].hasChildNodes()) {
G[B].firstChild.style.width=D;
var I=G[B].firstChild.rows[0];
for(var A=0;A<H;A++){
I.childNodes[A].style.width=F[A]
}
}
}
this.onAllColumnWidthsUpdated(F,D)
},
onColumnWidthsUpdated : function(cols, w, tw){
// template method
},
setFocusRowCell : function (focusRowCell) {
this.focusRowCell = focusRowCell;
},

processEvent: function(name, e){
Ext.grid.GroupingView.superclass.processEvent.call(this, name, e);
var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
if(hd){
// group value is at the end of the string
var field = this.getGroupField(),
prefix = this.getPrefix(field),
groupValue = hd.id.substring(prefix.length),
emptyRe = new RegExp('gp-' + Ext.escapeRe(field) + '--hd');

// remove trailing '-hd'
groupValue = groupValue.substr(0, groupValue.length - 3);

// also need to check for empty groups
if(groupValue || emptyRe.test(hd.id)){
this.grid.fireEvent('group' + name, this.grid, field, groupValue, e);
}

if(name == 'mousedown' && e.button == 0){
//this.toggleGroup(hd.parentNode);
this.toggleAllGroups();
var st = this.scroller.dom.scrollTop
var gel = Ext.fly(hd.parentNode);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;
this.scroller.dom.scrollTop = (headerOffsetY+st);
this.doUpdate();

}
}
}

});

SebTardif
8 Mar 2011, 1:20 PM
Your latest version has a bug, at least against Ext JS 3.3.1.

Error is: Cannot read property 'offsetHeight' of null

var groups = this.getGroups();
for(var i = 0, len = groups.length; i < len; i++){
var gel = Ext.fly(groups[i]);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;

rivarecords
9 Mar 2011, 10:08 AM
I'm not getting the error but commenting out that line won't hurt anything as it's not used in the processEvent function. Looks like it got copied from getVisisbleGroupHeadersHeight where similar work was done.

jej2003
10 Mar 2011, 9:58 AM
first off, thanks I needed this as well but made some modifications which allow individual groups to be collapsed. In the process I also took out the this.lastSavedVisibleRowsTemp functionality (I don't think there will be an issue with commenting it out, but it was causing me some issue while debugging). I have attached the code for anyone interested. I'd also be interested in any comments.


Ext.ux.BufferedGroupingView = Ext
.extend(
Ext.grid.GroupingView,
{
rowHeight : 19,
borderHeight : 2,
scrollDelay : 100,
cacheSize : 20,
cleanDelay : 500,
groupsList : [],
passedGroupHeadersHeight : 0,
passedGroupList : [],
lastSavedVisibleRowsTemp : null,
initTemplates : function() {
Ext.ux.BufferedGroupingView.superclass.initTemplates
.call(this);
var ts = this.templates;
// empty div to act as a place holder for a row
ts.rowHolder = new Ext.Template(
'<div class="x-grid3-row {alt}" style="{tstyle}"></div>');
ts.rowHolder.disableFormats = true;
ts.rowHolder.compile();
ts.rowBody = new Ext.Template(
'<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
'<tbody><tr>{cells}</tr>',
(this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>'
: ''), '</tbody></table>');
ts.rowBody.disableFormats = true;
ts.rowBody.compile();
},
getStyleRowHeight : function() {
return Ext.isBorderBox ? (this.rowHeight + this.borderHeight)
: this.rowHeight;
},
syncScroll : function() {
Ext.ux.BufferedGroupingView.superclass.syncScroll
.apply(this, arguments);
this.update();
},
// a (optionally) buffered method to update contents of gridview
update : function() {
if (this.scrollDelay) {
if (!this.renderTask) {
this.renderTask = new Ext.util.DelayedTask(
this.doUpdate, this);
}
this.renderTask.delay(this.scrollDelay);
} else {
this.doUpdate();
}
},
doUpdate : function() {
if (this.getVisibleRowCount() > 0) {
var g = this.grid, cm = g.colModel, ds = g.store;
var cs = this.getColumnData();
var vr = this.getVisibleRows();

for ( var i = vr.first; i <= vr.last; i++) {
// if row is NOT rendered and is visible, render it
if (!this.isRowRendered(i)) {
var html = this.doBufferViewRender(cs, [ ds
.getAt(i) ], ds, i, cm
.getColumnCount(), g.stripeRows,
true);
if (this.getRow(i)) {
this.getRow(i).innerHTML = html;
}
}
}
this.clean();
}
},
// a buffered method to clean rows
clean : function() {
if (!this.cleanTask) {
this.cleanTask = new Ext.util.DelayedTask(
this.doClean, this);
}
this.cleanTask.delay(this.cleanDelay);
},
doClean : function() {
if (this.getVisibleRowCount() > 0) {
var vr = this.getVisibleRows();
vr.first -= this.cacheSize;
vr.last += this.cacheSize;
var i = 0, rows = this.getRows();
// if first is less than 0, all rows have been rendered
// so lets clean the end...
if (vr.first <= 0) {
i = vr.last + 1;
}
for ( var len = this.ds.getCount(); i < len; i++) {
// if current row is outside of first and last and
// has content, update the innerHTML to nothing
if ((i < vr.first || i > vr.last)
&& rows[i].innerHTML) {
rows[i].innerHTML = '';
}
}
}
},
layout : function() {
Ext.ux.BufferedGroupingView.superclass.layout
.call(this);
this.update();
},
// private
doRender : function(cs, rs, ds, startRow, colCount, stripe) {
if (rs.length < 1) {
return '';
}
var groupField = this.getGroupField(), colIndex = this.cm
.findColumnIndex(groupField), g;
this.enableGrouping = !!groupField;
if (!this.enableGrouping || this.isUpdating) {
return this.doBufferViewRender.apply(this,
arguments, false);
}
var gstyle = 'width:' + this.getTotalWidth() + ';';
var gidPrefix = this.grid.getGridEl().id;
var cfg = this.cm.config[colIndex];
var groupRenderer = cfg.groupRenderer || cfg.renderer;
var prefix = this.showGroupName ? (cfg.groupName || cfg.header) + ': '
: '';
var groups = [], curGroup, i, len, gid;
for (i = 0, len = rs.length; i < len; i++) {
var rowIndex = startRow + i, r = rs[i], gvalue = r.data[groupField];

g = this.getGroup(gvalue, r, groupRenderer,
rowIndex, colIndex, ds);
if (!curGroup || curGroup.group != g) {
gid = gidPrefix + '-gp-' + groupField + '-'
+ Ext.util.Format.htmlEncode(g);
// if state is defined use it, however state is in terms of expanded
// so negate it, otherwise use the default.
var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid]
: this.startCollapsed;
var gcls = isCollapsed ? 'x-grid-group-collapsed'
: '';

//alert(gid);
curGroup = {
group : g,
gvalue : gvalue,
text : prefix + g,
groupId : gid,
startRow : rowIndex,
rs : [ r ],
cls : gcls,
style : gstyle
};
groups.push(curGroup);
} else {
curGroup.rs.push(r);
}
r._groupId = gid;
}
var buf = [];
for (i = 0, len = groups.length; i < len; i++) {
// maintain groups in an array.
this.groupsList[i] = groups[i];
g = groups[i];
this.doGroupStart(buf, g, cs, ds, colCount);
buf[buf.length] = this.doBufferViewRender(cs, g.rs,
ds, g.startRow, colCount, stripe, false);
this.doGroupEnd(buf, g, cs, ds, colCount);
}
groups = null;

return buf.join('');
},
getVisisbleGroupHeadersHeight : function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
this.passedGroupList = [];
var groups = this.getGroups();
for ( var i = 0, len = groups.length; i < len; i++) {
var gel = Ext.fly(groups[i]);
var headerOffsetY = gel
.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;

if (headerOffsetY < 0) {
this.passedGroupList.push(groups[i]);
this.passedGroupHeadersHeight += headerHeight;
}
if (headerOffsetY >= 0
&& headerOffsetY < visibleHeight) {
if ((headerOffsetY + headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight - headerOffsetY);
} else if (headerOffsetY < 0
&& (headerOffsetY + headerHeight) > 0)
totalGroupHeadersHeight += (headerOffsetY + headerHeight);

}

return totalGroupHeadersHeight;
},
getCalculatedRowHeight : function() {
return this.rowHeight + this.borderHeight;
},
getVisibleRowCount : function() {
var rh = this.getCalculatedRowHeight();
var visibleHeight = this.scroller.dom.clientHeight;
//already used height.
var uh = this.getVisisbleGroupHeadersHeight(visibleHeight);
//alert(Math.ceil((visibleHeight-uh) / rh));
return (visibleHeight < 1) ? 0 : Math
.ceil((visibleHeight - uh) / rh);
},
getVisibleRows : function() {

var st = this.scroller.dom.scrollTop;
// if (this.lastSavedVisibleRowsTemp != null
// && st == this.lastSavedVisibleRowsTemp.scroll
// && this.lastSavedVisibleRowsTemp.rows.first >= 0) {
// if (this.lastSavedVisibleRowsTemp.rows.last
// - this.lastSavedVisibleRowsTemp.rows.last)
// return this.lastSavedVisibleRowsTemp.rows;
// }
var count = this.getVisibleRowCount();


//we only need to find the groups which are above where we are currently looking
var collapsedRows = 0;
var groups = this.passedGroupList;
for(var x = 0; x < groups.length; x++){
if(this.isGroupCollapsed(groups[x])){
collapsedRows += Ext.get(groups[x]).first('.x-grid-group-body').dom.childElementCount;

}

}

var rowHeight = this.getCalculatedRowHeight();
st = st + collapsedRows * rowHeight;
var sc = st - this.passedGroupHeadersHeight;
console.log('st: ' + st);
console.log('sc: ' + sc);



// start 4 rows before to aviod empty space on top caused by random miscalculations.
var start = (sc == 0 ? 0 : Math.floor(sc
/ rowHeight) - 4);
console.log(rowHeight);



this.passedGroupHeadersHeight = 0;

console.log('initial start: ' + start);
console.log('initial last: ' + (start + count));

var first = start;
var last = start + count;

last = last + 4;

this.lastSavedVisibleRowsTemp = {
scroll : st,
rows : {
first : Math.max(first, 0),
last : Math.min(last, this.ds
.getCount() - 1)
}
};
return this.lastSavedVisibleRowsTemp.rows;
},
isGroupCollapsed : function(group){
var gel = Ext.get(group);
return gel.hasClass('x-grid-group-collapsed');
},
isRowRendered : function(index) {
var row = this.getRow(index);
return row && row.childNodes.length > 0;
},
doBufferViewRender : function(cs, rs, ds, startRow,
colCount, stripe, onlyBody) {
var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount - 1;
var rh = this.getStyleRowHeight();
var vr = this.getVisibleRows();
var tstyle = 'width:' + this.getTotalWidth()
+ ';height:' + rh + 'px;';
// buffers
var buf = [], cb, c, p = {}, rp = {
tstyle : tstyle
}, r;
for ( var j = 0, len = rs.length; j < len; j++) {
r = rs[j];
cb = [];
var rowIndex = (j + startRow);
var visible = rowIndex >= vr.first
&& rowIndex <= vr.last;
if (visible) {
for ( var i = 0; i < colCount; i++) {
c = cs[i];
p.id = c.id;
p.css = i == 0 ? 'x-grid3-cell-first '
: (i == last ? 'x-grid3-cell-last '
: '');
p.attr = p.cellAttr = "";
p.value = c.renderer(r.data[c.name], p, r,
rowIndex, i, ds);
p.style = c.style;
if (p.value == undefined || p.value === "") {
p.value = " ";
}
// BHM doesn't show dirty flag.
//if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
// p.css += ' x-grid3-dirty-cell';
//}
cb[cb.length] = ct.apply(p);
}
}
var alt = [];
if (stripe && ((rowIndex + 1) % 2 == 0)) {
alt[0] = "x-grid3-row-alt";
}
if (r.dirty) {
alt[1] = " x-grid3-dirty-row";
}
rp.cols = colCount;
if (this.getRowClass) {
alt[2] = this.getRowClass(r, rowIndex, rp, ds);
}
rp.alt = alt.join(" ");
rp.cells = cb.join("");
buf[buf.length] = !visible ? ts.rowHolder.apply(rp)
: (onlyBody ? rb.apply(rp) : rt.apply(rp));
}
return buf.join('');
},

// this function is over-loaded to improve the performance.
getRow : function(row) {
if (!this.enableGrouping) {
var rs = Ext.grid.GroupingView.superclass.getRows
.call(this);
return rs[row];
}
var g, gs = this.getGroups();
for ( var i = 0, len = gs.length; i < len; i++) {
g = gs[i].childNodes[1].childNodes;
if (g.length <= row) {
row -= g.length;
continue;
} else if (row >= 0)
return g[row];
}
},
// private
processRows : function(startRow, skipStripe) {
if (!this.ds || this.ds.getCount() < 1) {
return;
}
var rows = this.getRows();
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
var F = " x-grid3-row-alt ";
Ext.each(rows,
function(row, idx) {
row.rowIndex = idx;
if (!skipStripe) {
var A = ((idx + 1) % 2 == 0);
var G = (" " + row.className + " ")
.indexOf(F) != -1;

if (A && A != G) {
row.className += " x-grid3-row-alt"
} else if (A != G) {
row.className = row.className
.replace("x-grid3-row-alt",
"")
}
}
});
// add first/last-row classes
if (startRow === 0) {
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1])
.addClass(this.lastRowCls);
},
/**
* This method is re-written to support both buffering
* and Grouping.
*/
updateAllColumnWidths : function() {
var D = this.getTotalWidth();
var H = this.cm.getColumnCount();
var F = [];
for ( var B = 0; B < H; B++) {
F[B] = this.getColumnWidth(B);
}
this.innerHd.firstChild.firstChild.style.width = D;
for ( var B = 0; B < H; B++) {
var C = this.getHeaderCell(B);
C.style.width = F[B]
}
var G = this.getRows();
for ( var B = 0, E = G.length; B < E; B++) {
G[B].style.width = D;
// following condition must be verified for buffering grid.
if (G[B].hasChildNodes()) {
G[B].firstChild.style.width = D;
var I = G[B].firstChild.rows[0];
for ( var A = 0; A < H; A++) {
I.childNodes[A].style.width = F[A]
}
}
}
this.onAllColumnWidthsUpdated(F, D)
},
onColumnWidthsUpdated : function(cols, w, tw) {
// template method
},
setFocusRowCell : function(focusRowCell) {
this.focusRowCell = focusRowCell;
},

processEvent : function(name, e) {
Ext.grid.GroupingView.superclass.processEvent.call(
this, name, e);
var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
if (hd) {
// group value is at the end of the string
var field = this.getGroupField(), prefix = this
.getPrefix(field), groupValue = hd.id
.substring(prefix.length), emptyRe = new RegExp(
'gp-' + Ext.escapeRe(field) + '--hd');

// remove trailing '-hd'
groupValue = groupValue.substr(0,
groupValue.length - 3);

// also need to check for empty groups
if (groupValue || emptyRe.test(hd.id)) {
this.grid.fireEvent('group' + name, this.grid,
field, groupValue, e);
}

if (name == 'mousedown' && e.button == 0) {
this.toggleGroup(hd.parentNode);
//this.toggleAllGroups();
var st = this.scroller.dom.scrollTop
var gel = Ext.fly(hd.parentNode);
var headerOffsetY = gel
.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;
this.scroller.dom.scrollTop = (headerOffsetY + st);
this.doUpdate();

}
}
}

});

rivarecords
10 Mar 2011, 12:14 PM
I gave it a try and looking at the changes you made it seems like you are trying to handle showing rows below collapsed groups. Although, I still have the same issue with large record set not displaying data below collapsed groups after a certain amount of rows. Can you verify this works with 1000+ rows without paging; collapse all groups and expand the last group, is data visible?

jej2003
10 Mar 2011, 1:21 PM
hmm, I see the issue you're talking about. I must be calculating something incorrectly, but essentially what you're saying is correct. I attempted (obviously poorly) to allow collapsing individual rows and then recalculate what should be shown based on what is collapsed and what is in the current view. Clearly I screwed something up though.

rivarecords
10 Mar 2011, 2:44 PM
Donít feel bad, I attempted the same thing for a day and gave up hence my toggle all groups work around. My users noticed that individually collapsing and expanding groups was no longer working but were happy enough with the performance improvement that they were willing to live with it. At least it scrolls to the expanded group. When I have more time I will revisit it and take a look at your changes; I think you were heading in the right direction.

rspaeth
8 Jul 2011, 2:07 PM
Has anyone tried to implement a buffered grouping grid in ext4 yet?

SebTardif
8 Jul 2011, 2:18 PM
I have seen working (virtual scrolling or infinite scrolling) together with (simple grouping or column locking) with Ext JS 4.

But simple (grouping + column locking) at the same time than (virtual scrolling or infinite scrolling) don't work.

Niko_K
29 Jul 2011, 2:04 AM
I am also getting the error that SebTardif earlier mentioned, but not only in processEvent function.
The main problem is that i am getting the error also in the method "getVisisbleGroupHeadersHeight":


getVisisbleGroupHeadersHeight: function(visibleHeight) {
var totalGroupHeadersHeight = 0;
this.passedGroupHeadersHeight = 0;
var groups = this.getGroups();
for (var i = 0, len = groups.length; i < len; i++) {
var gel = Ext.fly(groups[i]);
var headerOffsetY = gel.getOffsetsTo(this.mainBody.dom.parentNode)[1];
var headerHeight = gel.dom.firstChild.offsetHeight;

if (headerOffsetY < 0) {
this.passedGroupHeadersHeight += headerHeight;
}
if (headerOffsetY >= 0 && headerOffsetY < visibleHeight) {
if ((headerOffsetY + headerHeight) <= visibleHeight)
totalGroupHeadersHeight += headerHeight;
else
totalGroupHeadersHeight += (visibleHeight - headerOffsetY);
} else if (headerOffsetY < 0 && (headerOffsetY + headerHeight) > 0) totalGroupHeadersHeight += (headerOffsetY + headerHeight);

}

return totalGroupHeadersHeight;
},

In this case it is not possible to comment the line, since the headerHeight is used to calculate the totalGroupHeadersHeight.

I am using ExtJS version 3.2.1, sebTardif was using version 3.3.1, so i am wondering under which version this extension was tested (older versions?).

Could you please give me a hint, how to fix the problem?

Kind Regards,
Niko

brat002
15 Dec 2011, 2:11 PM
I also have this error. Extjs 3.4. How fix it?

Rashikh
25 Jul 2013, 12:25 AM
Did any one have the same kinda implementation for GWT ?

I am in need for BufferedGridView for GXT...

vasser
26 Sep 2013, 5:53 AM
Hi,
Is there any possibility to have a buffered view without defined row height in Ext 3.4? I need to have rows with style "white-space: normal", so height of each row can be various.