PDA

View Full Version : Multigrouping in ExtJS 3.2.1



trvd1707
28 Oct 2011, 11:36 AM
Hi,

I found this code on this forum, but it had some problems when I tried to use. I made some changes to fix it to work when the group field is a date and when the group field is boolean. The changes were done just in the multiGroupingView, in the doRender function.

Here is the fixed code:

/**
* @author chander, adapted by SIDGEY
* Changes made by Teresa Van Dusen: groups by date and by boolean values
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//------------------------------------- MULTI GROUPING STORE ----------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////////////////////////////////
Ext.ux.MultiGroupingStore = Ext.extend(Ext.data.GroupingStore, {
constructor: function(config){Ext.ux.MultiGroupingStore.superclass.constructor.apply(this, arguments);},
sortInfo: [],
sort: function(field, dir){
var f = [];
if (Ext.isArray(field)) {for (var i = 0, len = field.length; i < len; ++i) {f.push(this.fields.get(field[i]));}}
else {f.push(this.fields.get(field));}
if (f.length < 1) {return false;}
if (!dir) {
if (this.sortInfo && this.sortInfo.length > 0 && this.sortInfo[0].field == f[0].name)
{
// toggle sort direction
dir = (this.sortToggle[f[0].name] || "ASC").toggle("ASC", "DESC");
}
else
dir = f[0].sortDir;
}
var st = (this.sortToggle) ? this.sortToggle[f[0].name] : null;
var si = (this.sortInfo) ? this.sortInfo : null;
this.sortToggle[f[0].name] = dir;
this.sortInfo = [];
for (i=0, len=f.length; i < len; ++i) {
this.sortInfo.push({field: f[i].name,direction: dir});
}
if (!this.remoteSort) {
this.applySort();
this.fireEvent("datachanged", this);
}
else {
if (!this.load(this.lastOptions)){
if (st){this.sortToggle[f[0].name] = st;}
if (si){this.sortInfo = si;}
}
}
return true;
},
setDefaultSort: function (field, dir){
dir = dir ? dir.toUpperCase() : "ASC";
this.sortInfo = [];
if (!Ext.isArray(field)){this.sortInfo.push({field: field, direction: dir});}
else {
for (var i = 0, len = field.length; i < len; ++i) {
this.sortInfo.push({field: field[i].field,direction: dir});
this.sortToggle[field[i]] = dir;
}
}
},
groupBy: function (field, forceRegroup){
if (!forceRegroup && this.groupField == field)
return; // already grouped by this field
if (this.groupField)
{
for (var z = 0; z < this.groupField.length; z++)
{
if (field == this.groupField[z])
return;
}
this.groupField.push(field);
}
else {this.groupField = [field];}
if (this.remoteGroup) {
if (!this.baseParams) {this.baseParams = {};}
this.baseParams['groupBy'] = field;
}
if (this.groupOnSort) {this.sort(field); return;}
if (this.remoteGroup) { this.reload(); }
else {
var si = this.sortInfo || [];
if (si.field != field) {this.applySort();}
else { this.sortData(field);}
this.fireEvent('datachanged', this);
}
},
applySort: function (){
var si = this.sortInfo;
if (si && si.length > 0 && !this.remoteSort) {this.sortData(si, si[0].direction); }
if (!this.groupOnSort && !this.remoteGroup) {
var gs = this.getGroupState();
if (gs && gs != this.sortInfo) {this.sortData(this.groupField);}
}
},
getGroupState: function (){
return this.groupOnSort && this.groupField !== false ? (this.sortInfo ? this.sortInfo : undefined) : this.groupField;
},
sortData: function (flist, direction){
direction = direction || 'ASC';
var st = [];
var o;
for (var i = 0, len = flist.length; i < len; ++i) {
o = flist[i];
st.push(this.fields.get(o.field ? o.field : o).sortType);
}
var fn = function (r1, r2){
var v1 = [];
var v2 = [];
var len = flist.length;
var o;
var name;
for (i = 0; i < len; ++i) {
o = flist[i];
name = o.field ? o.field : o;
v1.push(st[i](r1.data[name]));
v2.push(st[i](r2.data[name]));
}
var result=0;
for (i = 0; i < len; ++i) {
result = v1[i] > v2[i] ? 1 : (v1[i] < v2[i] ? -1 : 0);
if (result !== 0){return result;}
}
return result; // if it gets here, that means all fields are equal
};
this.data.sort(direction, fn);
if (this.snapshot && this.snapshot != this.data) {this.snapshot.sort(direction, fn);}
}
});


Ext.ux.MultiGroupingView = Ext.extend(Ext.grid.GroupingView, {
constructor: function(config){
Ext.ux.MultiGroupingView.superclass.constructor.apply(this, arguments);
// Added so we can clear cached rows each time the view is refreshed
this.on("beforerefresh", function() {
if (this.rowsCache)
delete rowsCache;
}, this);
},
/* updated version of the updateGroupWidths from GroupingView:
* improve performance by keeping value of this.el.dom.offsetWidth
* in member of this class.
*/
offsetWidth: 0,
updateGroupWidths : function(){
if(!this.enableGrouping || !this.hasRows()){
return;
}
if (!this.offsetWidth)
this.offsetWidth = this.el.dom.offsetWidth;
var tw = Math.max(this.cm.getTotalWidth(),
this.offsetWidth-this.getScrollOffset()) +'px';
var gs = this.getGroups();
for(var i = 0, len = gs.length; i < len; i++){
gs[i].firstChild.style.width = tw;
}
},
/* updated version of GridView::syncHeaderScroll */
headerScrollSynced: false,
syncHeaderScroll : function(){
// if (this.headerScrollSynced)
// return;
this.headerScrollSynced = true;
var mb = this.scroller.dom;
this.innerHd.scrollLeft = mb.scrollLeft;
this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
},


get_column_by_id: function(id)
{
for (i in this.cm.lookup)
{
if (this.cm.lookup[i].dataIndex == id)
return this.cm.lookup[i];
}
return null;
},
getGroups: function()
{
return Ext.DomQuery.select("div.x-grid-group", this.mainBody.dom);
}
,displayEmptyFields: false
,displayFieldSeperator: ', '
,renderRows: function(){
var groupField = this.getGroupField();
var eg = !!groupField;
// if they turned off grouping and the last grouped field is hidden
if (this.hideGroupedColumn) {
var colIndexes = [];
for (var i = 0, len = groupField.length; i < len; ++i) {
var cidx=this.cm.findColumnIndex(groupField[i]);
if(cidx>=0){colIndexes.push(cidx);}
}
if (!eg && this.lastGroupField !== undefined) {
this.mainBody.update('');
for (var i = 0, len = this.lastGroupField.length; i < len; ++i) {
var cidx=this.cm.findColumnIndex(this.lastGroupField[i]);
if(cidx>=0){this.cm.setHidden(cidx, false);}
}
delete this.lastGroupField;
delete this.lgflen;
}
else if (eg && colIndexes.length > 0 && this.lastGroupField === undefined) {
this.lastGroupField = groupField;
this.lgflen = groupField.length;
for (var i = 0, len = colIndexes.length; i < len; ++i)
this.cm.setHidden(colIndexes[i], true);
}
else if (eg && this.lastGroupField !== undefined &&
(groupField !== this.lastGroupField || this.lgflen != this.lastGroupField.length))
{
this.mainBody.update('');
for (var i = 0, len = this.lastGroupField.length; i < len; ++i) {
var cidx=this.cm.findColumnIndex(this.lastGroupField[i]);
if(cidx>=0){this.cm.setHidden(cidx, false);}
}
this.lastGroupField = groupField;
this.lgflen = groupField.length;
for (var i = 0, len = colIndexes.length; i < len; ++i) {
this.cm.setHidden(colIndexes[i], true);
}
}
}
return Ext.ux.MultiGroupingView.superclass.renderRows.apply(this, arguments);
}
,
// this collection keeps a fast reference for group memberships for each record
// it is not kept in the record since it would be destroyed during edit
// it is used to do a fast update of summaries
record_memberships: []
/** This sets up the toolbar for the grid based on what is grouped
* It also iterates over all the rows and figures out where each group should appeaer
* The store at this point is already stored based on the groups.
*/
,doRender: function(cs, rs, ds, startRow, colCount, stripe){
var ss = this.grid.getTopToolbar();
if (rs.length < 1) {return '';}
var groupField = this.getGroupField();
var gfLen = groupField.length;
for (var i = 0; i < cs.length; i++)
cs[i].style = this.getColumnStyle(i, false);


ss.removeAll();
// Remove all entries alreay in the toolbar
//for (var hh = 0; hh < ss.items.length; hh++)
// Ext.removeNode(Ext.getDom(ss.items.itemAt(hh).id));


if (gfLen==0)
ss.addItem(new Ext.Toolbar.TextItem("Drop Columns Here To Group"));
else {
// Add back all entries to toolbar from GroupField[]
ss.addItem(new Ext.Toolbar.TextItem("Grouped By:"));
for (var gfi = 0; gfi < gfLen; gfi++) {
var t = groupField[gfi];
if (gfi>=0) {
ss.addItem(new Ext.Toolbar.Separator());
var b = new Ext.Toolbar.Button({
text: this.get_column_by_id(t).header
});
b.fieldName = t;
ss.addItem(b);
}
}
}
ss.doLayout();

this.enableGrouping = !!groupField;
if (!this.enableGrouping || this.isUpdating)
return Ext.grid.GroupingView.superclass.doRender.apply(this, arguments);


var gstyle = 'width:' + this.getTotalWidth() + ';';
var gidPrefix = this.grid.getGridEl().id;
var groups = [], curGroup, i, len, gid;
var lastvalues = [];
var added = 0;
var currGroups = [];


// Create a specific style
var st = Ext.get(gidPrefix+"-style");
if(st)
st.remove();
var html_code =
"div#" + gidPrefix +
" div.x-grid3-row {padding-left:" + (gfLen*12) + "px}" +
"div#" + gidPrefix + " div.x-grid3-header {padding-left:" + (gfLen*12) + "px}";
Ext.getDoc().child("head").createChild({
tag: 'style',
id: gidPrefix + "-style",
html: html_code
});
// traverse all rows in grid
for (var i = 0, len = rs.length; i < len; i++) {
added = 0;
var rowIndex = startRow + i;
var r = rs[i];
var differ = 0;
var gvalue = [];
var fieldName;
var fieldLabel;
var grpFieldNames = [];
var grpFieldLabels = [];
var v;
var changed = 0;
var addGroup = [];
var member_of_groups = [];
this.record_memberships[r.id] = member_of_groups;
// check group fields to see if we have a different group
for (var j = 0; j < gfLen; j++) {
fieldName = groupField[j];
fieldLabel = this.get_column_by_id(fieldName).header;
v = r.data[fieldName];
if ((v && typeof v != 'boolean') || typeof v == 'boolean') {
if (i == 0) {
// First record always starts a new group
addGroup.push({idx:j,dataIndex:fieldName,header:fieldLabel,value:v});
lastvalues[j] = v;
gvalue.push(v);
grpFieldNames.push(fieldName);
grpFieldLabels.push(fieldLabel + ': ' + v);
}
else {
if ( (!(v instanceof Date) && lastvalues[j]!== v)&& typeof v !='boolean'
||( v instanceof Date && (lastvalues[j]-v)!=0)) {
// This record is not in same group as previous one
addGroup.push({idx:j,dataIndex:fieldName,header:fieldLabel,value:v});
lastvalues[j] = v;
changed = 1;
gvalue.push(v);
grpFieldNames.push(fieldName);
grpFieldLabels.push(fieldLabel + ': ' + v);
} else if (typeof v == 'boolean' && typeof lastvalues[j]== 'boolean' && ((lastvalues[j] && !v) || (!lastvalues[j]&& v))){
addGroup.push({idx:j,dataIndex:fieldName,header:fieldLabel,value:v});
lastvalues[j] = v;
changed = 1;
gvalue.push(v);
grpFieldNames.push(fieldName);
grpFieldLabels.push(fieldLabel + ': ' + v);

}else{
if (gfLen-1 == j && changed != 1)
{
// This row is in all the same groups to the previous group
curGroup.rs.push(r);
member_of_groups.push(curGroup);
}
else if (changed == 1) {
// This group has changed because an earlier group changed.
addGroup.push({idx:j,dataIndex:fieldName,header:fieldLabel,value:v});
gvalue.push(v);
grpFieldNames.push(fieldName);
grpFieldLabels.push(fieldLabel + ': ' + v);
}
else if(j<gfLen-1) {
var parent_group = currGroups[fieldName];
// This is a parent group, and this record is part of this parent so add it
if (parent_group)
{
parent_group.rs.push(r);
member_of_groups.push(parent_group);
}
}
}
}
}
else {
if (this.displayEmptyFields) {
addGroup.push({idx:j,dataIndex:fieldName,header:fieldLabel,value:this.emptyGroupText||'(none)'});
grpFieldNames.push(fieldName);
grpFieldLabels.push(fieldLabel + ': ');
}
}
} // end of "for j"


// build current group record
for (var k = 0; k < addGroup.length; k++) {
var gp = addGroup[k];
g = gp.dataIndex;
var glbl = addGroup[k].header;
N = this.cm.findColumnIndex(g);
var F = this.cm.config[N];
var B = F.groupRenderer||F.renderer;
var S = this.showGroupName ? (F.groupName || F.header) + ": " : "";
V = this.getGroup(gp.value, r, B, i, N, ds);
gid = gidPrefix + '-gp-' + gp.dataIndex + '-' +
Ext.util.Format.htmlEncode(gp.value);


// 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: gp.dataIndex,
gvalue: V,
key: r.data[gp.dataIndex], // current grouping key
text: gp.header,
groupId: gid,
group_level: gp.idx,
startRow: rowIndex,
rs: [r],
cls: gcls,
style: gstyle + 'padding-left:' + (gp.idx * 12) + 'px;'
};
currGroups[gp.dataIndex] = curGroup;
groups.push(curGroup);
r._groupId = gid; // Associate this row to a group
member_of_groups.push(curGroup);
if (typeof this.groups == "undefined")
this.groups = new Array();
this.groups.push(curGroup);
} // end of "for k"
} // end of "for i"


var buf = [];
var parents_queued = [];
var queue_count = 0;
for (var ilen = 0, len = groups.length; ilen < len; ilen++) {
var g = groups[ilen];
var next_group = groups[ilen + 1];
var cur_level = g.group_level;
var next_level = next_group == null ? -1 : next_group.group_level;
var leaf = g.group == groupField[gfLen - 1]
this.doGroupStart(buf, g, cs, ds, colCount);
if (g.rs.length != 0 && leaf)
{
buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call(
this, cs, g.rs, ds, g.startRow, colCount, stripe);
}
if (leaf) {
// do summaries on all grouping levels for this group
this.doGroupEnd(buf, g, cs, ds, colCount);
}
else
parents_queued.push(g);
if (next_level >= cur_level)
continue;
// going back from leaf - pop parents from queue
// and call doGroupEnd for each
while (cur_level > next_level && cur_level > 0)
{
g = parents_queued.pop();
this.doGroupEnd(buf, g, cs, ds, colCount);
cur_level--;
}
}
return buf.join('');
},
getGroup:function(A,D,F,G,B,E)
{
var C = F ? F(A,{},D,G,B,E) : String(A);
if (C==="")
C = this.cm.config[B].emptyGroupText || this.emptyGroupText;
return C;
},
/** Should return an array of all elements that represent a row, it should bypass
* all grouping sections
*/
getRows: function(){
if (!this.enableGrouping)
return Ext.grid.GroupingView.superclass.getRows.call(this);


return Ext.DomQuery.select("div.x-grid3-row", this.mainBody.dom);
},
getGroupById: function(gid)
{
var g = null;
for (var i = 0; i < this.groups.length; i++)
{
group = this.groups[i];
if (group.groupId == gid)
return group;
}
return g;
},
/* override the processEvent function of groupingView to handle groupField
* which is an array of fields
*/
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();
// in MultiGrouping field is an array of field names, so take just
// the last field name
if (typeof field == "object" && field.length)
field = field[field.length-1];
var prefix = this.getPrefix(field);
var groupValue = hd.id.substring(prefix.length);
var 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);
}
}
}
});


Ext.ux.MultiGroupingPanelEditor = function(config) {
config = config||{};
config.tbar = new Ext.Toolbar({id:'grid-tbr'});
Ext.ux.MultiGroupingPanelEditor.superclass.constructor.call(this, config);
};


Ext.extend(Ext.ux.MultiGroupingPanelEditor, Ext.grid.GridPanel,{
isEditor:true,
detectEdit:false,
autoEncode:false,
trackMouseOver:false,
initComponent:function(){
Ext.ux.MultiGroupingPanelEditor.superclass.initComponent.call(this);
if(!this.selModel){
this.selModel=new Ext.grid.CellSelectionModel()
}
this.activeEditor=null;
this.addEvents("beforeedit","afteredit","validateedit")
}
,initEvents:function(){
Ext.ux.MultiGroupingPanelEditor.superclass.initEvents.call(this);
this.on("bodyscroll",this.stopEditing,this,[true]);
if(this.clicksToEdit==1){
this.on("cellclick",this.onCellDblClick,this)
}
else{
if(this.clicksToEdit=="auto"&&this.view.mainBody){this.view.mainBody.on("mousedown",this.onAutoEditClick,this)}
this.on("celldblclick",this.onCellDblClick,this)
}this.getGridEl().addClass("xedit-grid")
}
,onCellDblClick:function(B,C,A){this.startEditing(C,A)}
,onAutoEditClick:function(C,B){
if(C.button!==0){return }
var E=this.view.findRowIndex(B);
var A=this.view.findCellIndex(B);
if(E!==false&&A!==false){
this.stopEditing();
if(this.selModel.getSelectedCell){
var D=this.selModel.getSelectedCell();
if(D&&D.cell[0]===E&&D.cell[1]===A){this.startEditing(E,A)}
}
else{if(this.selModel.isSelected(E)){ this.startEditing(E,A)}}
}
}
,onEditComplete:function(B,D,A){
this.editing=false;
this.activeEditor=null;
B.un("specialkey",this.selModel.onEditorKey,this.selModel);
var C=B.record;
var F=this.colModel.getDataIndex(B.col);
D=this.postEditValue(D,A,C,F);
if(String(D)!==String(A))
{
var E={
grid:this,
record:C,
field:F,
originalValue:A,
value:D,
row:B.row,
column:B.col,
cancel:false
};
if (this.fireEvent("validateedit",E) !== false && !E.cancel)
{
C.set(F,E.value);
delete E.cancel;
this.fireEvent("afteredit",E);
}
}
this.view.focusCell(B.row,B.col)
}
,startEditing:function(F,B)
{
this.stopEditing();
if(this.colModel.isCellEditable(B,F))
{
this.view.ensureVisible(F,B,true);
var C=this.store.getAt(F);
var E=this.colModel.getDataIndex(B);
var D = {
grid:this,
record:C,
field:E,
value:C.data[E],
row:F,
column:B,
cancel:false
};
if (this.fireEvent("beforeedit",D)!==false&&!D.cancel)
{
this.editing=true;
var A = this.colModel.getCellEditor(B,F);
if (!A.rendered)
A.render(this.view.getEditorParent(A));


(function()
{
A.row=F;
A.col=B;
A.record=C;
A.on("complete", this.onEditComplete, this, {single:true});
A.on("specialkey", this.selModel.onEditorKey, this.selModel);
this.activeEditor=A;
var G = this.preEditValue(C,E);
A.startEdit(this.view.getCell(F,B),G);
}).defer(50,this);
}
}
}
,preEditValue:function(A,B){return this.autoEncode&&typeof value=="string"?Ext.util.Format.htmlDecode(A.data[B]):A.data[B]}
,postEditValue:function(C,A,B,D){return this.autoEncode&&typeof C=="string"?Ext.util.Format.htmlEncode(C):C}
,stopEditing:function(A){if(this.activeEditor){this.activeEditor[A===true?"cancelEdit":"completeEdit"]()}this.activeEditor=null}
,setUpDragging: function( ){
this.dragZone = new Ext.dd.DragZone(this.getTopToolbar().getEl(), {
ddGroup:"grid-body"
,panel:this
,scroll:false
,onInitDrag : function(e) {
var clone = this.dragData.ddel;
clone.id = Ext.id('ven');
this.proxy.update(clone);
return true;
}
,getDragData: function(e) {
var target = Ext.get(e.getTarget().id);
if(target.hasClass('x-toolbar x-small-editor')) { return false;}
d = e.getTarget().cloneNode(true);
d.id = Ext.id();
this.dragData = {
repairXY: Ext.fly(target).getXY(),
ddel: d,
btn:e.getTarget()
};
return this.dragData;
}
,getRepairXY: function() {return this.dragData.repairXY;}
});
this.dropTarget2s = new Ext.dd.DropTarget('grid-tbr', {
ddGroup: "gridHeader" + this.getGridEl().id
,panel:this
,notifyDrop: function(dd, e, data) {
var btname= this.panel.getColumnModel().getDataIndex( this.panel.getView().getCellIndex(data.header));
this.panel.store.groupBy(btname);
return true;
}
});
this.dropTarget22s = new Ext.dd.DropTarget(this.getView().el.dom.childNodes[0].childNodes[1], {
ddGroup: "grid-body"
,panel:this
,notifyDrop: function(dd, e, data) {
var txt = Ext.get(data.btn).dom.innerHTML;
var tb = this.panel.getTopToolbar();
var bidx = tb.items.findIndexBy(function(b) {
return b.text==txt;
},this);
if(bidx<0) return false;
var fld = tb.items.get(bidx).fieldName;
Ext.removeNode(Ext.getDom(tb.items.get(bidx).id));
if(bidx>0) Ext.removeNode(Ext.getDom(tb.items.get(bidx-1).id));;
var cidx=this.panel.view.cm.findColumnIndex(fld);
if(cidx<0){}
this.panel.view.cm.setHidden(cidx, false);
var temp=[];
for(var i=this.panel.store.groupField.length-1;i>=0;i--) {
if(this.panel.store.groupField[i]==fld) {
this.panel.store.groupField.pop();
break;
}
temp.push(this.panel.store.groupField[i]);
this.panel.store.groupField.pop();
}
for(var i=temp.length-1;i>=0;i--) {this.panel.store.groupField.push(temp[i]);}
if(this.panel.store.groupField.length==0){this.panel.store.groupField=false;}
this.panel.store.fireEvent('datachanged', this);
return true;
}
});
}
});
Ext.reg("editorgrid",Ext.ux.MultiGroupingPanelEditor);


Ext.ux.MultiGroupingPanel = function(config) {
config = config||{};
config.tbar = new Ext.Toolbar({id:'grid-tbr'});
Ext.ux.MultiGroupingPanel.superclass.constructor.call(this, config);
};


Ext.extend(Ext.ux.MultiGroupingPanel, Ext.grid.GridPanel, {
initComponent : function(){
Ext.ux.MultiGroupingPanel.superclass.initComponent.call(this);
this.on("render", this.setUpDragging, this);
}
,setUpDragging: function() {
this.dragZone = new Ext.dd.DragZone(this.getTopToolbar().getEl(), {
ddGroup:"grid-body"
,panel:this
,scroll:false
,onInitDrag : function(e) {
var clone = this.dragData.ddel;
clone.id = Ext.id('ven');
this.proxy.update(clone);
return true;
}
,getDragData: function(e) {
var target = Ext.get(e.getTarget().id);
if (target === undefined) return false;
if(target.hasClass('x-toolbar x-small-editor')) {
return false;
}
d = e.getTarget().cloneNode(true);
d.id = Ext.id();
this.dragData = {
repairXY: Ext.fly(target).getXY(),
ddel: d,
btn:e.getTarget()
};
return this.dragData;
}
,getRepairXY: function() { return this.dragData.repairXY; }
});
this.dropTarget2s = new Ext.dd.DropTarget('grid-tbr', {
ddGroup: "gridHeader" + this.getGridEl().id
,panel:this
,notifyDrop: function(dd, e, data) {
var btname= this.panel.getColumnModel().getDataIndex( this.panel.getView().getCellIndex(data.header));
this.panel.store.groupBy(btname);
return true;
}
});
this.dropTarget22s = new Ext.dd.DropTarget(this.getView().el.dom.childNodes[0].childNodes[1], {
ddGroup: "grid-body"
,panel:this
,notifyDrop: function(dd, e, data) {
var txt = Ext.get(data.btn).dom.innerHTML;
var tb = this.panel.getTopToolbar();
var bidx = tb.items.findIndexBy(function(b) {
return b.text==txt;
},this);
if(bidx<0) return false;
var fld = tb.items.get(bidx).fieldName;
Ext.removeNode(Ext.getDom(tb.items.get(bidx).id));
if(bidx>0) Ext.removeNode(Ext.getDom(tb.items.get(bidx-1).id));;
var cidx=this.panel.view.cm.findColumnIndex(fld);
if(cidx<0){}
this.panel.view.cm.setHidden(cidx, false);
var temp=[];
for(var i=this.panel.store.groupField.length-1;i>=0;i--) {
if(this.panel.store.groupField[i]==fld) {
this.panel.store.groupField.pop();
break;
}
temp.push(this.panel.store.groupField[i]);
this.panel.store.groupField.pop();
}
for(var i=temp.length-1;i>=0;i--) {this.panel.store.groupField.push(temp[i]);}
if(this.panel.store.groupField.length==0){this.panel.store.groupField=false;}


this.panel.store.fireEvent('datachanged', this);
return true;
}
});
}
});


Ext.ux.MultiGroupingGrid = function(config) {
config = config||{};

// Cache the orignal column model, before state is applied
if(config.cm)
this.origColModel = Ext.ux.util.clone(config.cm.config);
else if(config.colModel)
this.origColModel = Ext.ux.util.clone(config.colModel.config);

config.tbar = [{
xtype:'tbtext'
,text:this.emptyToolbarText
},{
xtype:'tbfill'
,noDelete:true
},{
xtype:'tbbutton'
,text:'Options'
,noDelete:true
,menu:{
items: [{
text:'Columns Reset',
scope: this,
disabled: !this.origColModel,
handler: function() {
this.getColumnModel().setConfig(this.origColModel,false);
this.saveState();
return true;
}
},{
text:'Show columns grouped'
,checked: !config.view.hideGroupedColumn
,scope:this
,checkHandler: function (item, checked) {
this.view.hideGroupedColumn = !checked;
this.view.refresh(true);
}
},{
text: 'Clean filters' // Labels.get('label.jaffa.jaffaRIA.jaffa.finder.grid.deactivateFilters')
,scope: this
,handler: function () {
//@TODO use the clearFilters() method!
this.plugins.filters.each(function(flt) {
flt.setActive(false);
});
}
}]
}
}];
Ext.ux.MultiGroupingGrid.superclass.constructor.call(this, config);
//console.debug("Create MultiGroupingGrid",config);
};


//Ext.extend(Ext.ux.MultiGroupingGrid, Ext.grid.GridPanel, {
//modified by jesus, extend a Ext.grid.EditorGridPanel class
Ext.extend(Ext.ux.MultiGroupingGrid, Ext.grid.EditorGridPanel, {




initComponent : function(){
//console.debug("MultiGroupingGrid.initComponent",this);
Ext.ux.MultiGroupingGrid.superclass.initComponent.call(this);

// Initialise DragZone
this.on("render", this.setUpDragging, this);
}

/** @cfg emptyToolbarText String to display on tool bar when there are no groups
*/
,emptyToolbarText : "Drag the columns to group here"
/** Extend basic version so the Grouping Columns State is remebered
*/
,getState : function(){
var s = Ext.ux.MultiGroupingGrid.superclass.getState.call(this);
s.groupFields = this.store.getGroupState();
return s;
}

/** Extend basic version so the Grouping Columns State is applied
*/
,applyState : function(state){
Ext.ux.MultiGroupingGrid.superclass.applyState.call(this,state);
if(state.groupFields) {
this.store.groupBy(state.groupFields,true);
if (typeof console != "undefined")
console.debug("Grid.applyState: Groups=",state.groupFields);
}
}

,setUpDragging: function() {
//console.debug("SetUpDragging", this);
this.dragZone = new Ext.dd.DragZone(this.getTopToolbar().getEl(), {
ddGroup:"grid-body" + this.getGridEl().id //FIXME - does this need to be unique to support multiple independant panels on the same page
,panel:this
,scroll:false
// @todo - docs
,onInitDrag : function(e) {
// alert('init');
var clone = this.dragData.ddel;
clone.id = Ext.id('ven'); //FIXME??
// clone.class='x-btn button';
this.proxy.update(clone);
return true;
}


// @todo - docs
,getDragData: function(e) {
var target = Ext.get(e.getTarget().id);
//console.debug("DragZone: ",e,target);
if(!target || target.hasClass('x-toolbar x-small-editor')) {
return false;
}

d = e.getTarget().cloneNode(true);
d.id = Ext.id();
if (typeof console != "undefined")
console.debug("getDragData",this, target);

this.dragData = {
repairXY: Ext.fly(target).getXY(),
ddel: d,
btn:e.getTarget()
};
return this.dragData;
}


//Provide coordinates for the proxy to slide back to on failed drag.
//This is the original XY coordinates of the draggable element.
,getRepairXY: function() {
return this.dragData.repairXY;
}
});

// This is the target when columns are dropped onto the toolbar (ie added to the group)
this.dropTarget2s = new Ext.dd.DropTarget(this.getTopToolbar().getEl(), {
ddGroup: "gridHeader" + this.getGridEl().id
,panel:this
,notifyDrop: function(dd, e, data) {
if (this.panel.getColumnModel().config[this.panel.getView().getCellIndex(data.header)].groupable) {
if (typeof console != "undefined")
console.debug("Adding Filter", data);
var btname = this.panel.getColumnModel().getDataIndex(this.panel.getView().getCellIndex(data.header));
this.panel.store.groupBy(btname);
return true;
}
else {
return false;
}
}
,notifyOver: function(dd,e,data){
if (this.panel.getColumnModel().config[this.panel.getView().getCellIndex(data.header)].groupable) {
return this.dropAllowed;
}
else {
return this.dropNotAllowed;
}
}
});


// This is the target when columns are dropped onto the grid (ie removed from the group)
this.dropTarget22s = new Ext.dd.DropTarget(this.getView().el.dom.childNodes[0].childNodes[1], {
ddGroup: "grid-body" + this.getGridEl().id //FIXME - does this need to be unique to support multiple independant panels on the same page
,panel:this
,notifyDrop: function(dd, e, data) {
var txt = Ext.get(data.btn).dom.innerHTML;
var tb = this.panel.getTopToolbar();
if (typeof console != "undefined")
console.debug("Removing Filter", txt);
var bidx = tb.items.findIndexBy(function(b) {
if (typeof console != "undefined")
console.debug("Match button ",b.text);
return b.text==txt;
},this);
if (typeof console != "undefined")
console.debug("Found matching button", bidx);
if(bidx<0) return false; // Error!
var fld = tb.items.get(bidx).fieldName;

// Remove from toolbar
Ext.removeNode(Ext.getDom(tb.items.get(bidx).id));
if(bidx>0) Ext.removeNode(Ext.getDom(tb.items.get(bidx-1).id));;


if (typeof console != "undefined")
console.debug("Remove button", fld);
//console.dir(button);
var cidx=this.panel.view.cm.findColumnIndex(fld);

if(cidx<0)
console.error("Can't find column for field ", fld);

this.panel.view.cm.setHidden(cidx, false);


//Ext.removeNode(Ext.getDom(data.btn.id));


// Remove this group from the groupField array
// @todo - replace with method on store
// this.panel.store.removeGroupField(fld);
var temp=[];
for(var i=this.panel.store.groupField.length-1;i>=0;i--) {
if(this.panel.store.groupField[i]==fld) {
this.panel.store.groupField.pop();
break;
}
temp.push(this.panel.store.groupField[i]);
this.panel.store.groupField.pop();
}


for(var i=temp.length-1;i>=0;i--) {
this.panel.store.groupField.push(temp[i]);
}


if(this.panel.store.groupField.length==0)
this.panel.store.groupField=false;


this.panel.store.fireEvent('datachanged', this);
return true;
}
});
}


,buildFilters: function (columns, record) {
//console.debug("Grid.buildFilters: Created Filters from ", columns, record);
var config = [];
for(var i=0;i<columns.length;i++) {
var col = columns[i];
var meta = record.getField(col.dataIndex);
//console.debug("Meta Data For ", col.dataIndex, meta)
if(meta && (meta.filter || meta.filterFieldName)) {
var dt = meta.dataType || 'string';
if (dt=='int' || dt=='long' || dt=='float' || dt=='double')
dt=='numeric';
else if (dt=='dateonly' || dt=='datetime')
dt='date';
//FIXME pass caseType on this filter definition, so it can be applied to the filter field
var f = {dataIndex:col.dataIndex, type:dt, paramName:col.filterFieldName};
config[config.length] = f;
}
}
if (typeof console != "undefined")
console.debug("Grid.buildFilters: Created Filters for ", config);
if(config.length==0)
return null;
else
return new Ext.ux.GridFilters({filters:config, local:false});
}


,buildColumnModel: function (columns, record) {
var config = [];
for(var i=0;i<columns.length;i++) {
var col = columns[i];
var meta = record.getField(col.dataIndex);
var cm = Ext.apply({},col);
if(meta) {
// Apply stuff from the Record's Meta Data
if(!cm.hidden && meta.hidden==true) cm.hidden = true;
if(!cm.header && meta.label) cm.header = meta.label;
if(!cm.renderer && meta.renderer) cm.renderer = meta.renderer;
cm.sortable=(meta.sortable===true || (meta.sortFieldName&&meta.sortFieldName!=''));


// Apply more metadata from associated ClassMetaData
var mc = meta.metaClass || record.defaultMetaClass;
var mfn = (meta.mapping||col.dataIndex).match(/.*\b(\w+)$/)[1];
var mf = ClassMetaData[mc]?ClassMetaData[mc].fields[mfn]:undefined;
if(!mf) mf = ClassMetaData[mc]?ClassMetaData[mc].fields[col.dataIndex]:undefined;
if (typeof console != "undefined")
console.debug("Meta Class=",mc,ClassMetaData[mc],', dataIndex=',col.dataIndex,', mapping=',meta.mapping,', mfn=',mfn,', mf=',mf,', meta=',meta);
if(mf) {
// Default the header text
if(!cm.header && mf.label) cm.header=mf.label;
// Default the column width
if(!cm.width) {
if(mf.maxLength) cm.width = Math.min(Math.max(mf.maxLength,5),40)*8;
else if(mf.type) {
if(mf.type=='dateonly') cm.width=100;
else if(mf.type=='datetime') cm.width=140;
else if(mf.type=='boolean') cm.width=50;
}
}
// Default the alignment
if(!cm.align && mf.type && (mf.type=='float'||mf.type=='int'))
cm.align = 'right';
// Default standard renderers
if(!cm.renderer && mf.type) {
if(mf.type=='dateonly') cm.renderer = Ext.util.Format.dateRenderer();
else if(mf.type=='datetime') cm.renderer = Ext.util.Format.dateTimeRenderer();
}
if(mf.hidden==true) cm.hidden = true;
}
}
if(!cm.header) cm.header = col.dataIndex;
cm.groupable = (cm.groupable==true || cm.sortable==true);
config[config.length] = cm;
if (typeof console != "undefined")
console.debug("Grid.buildColumnModel: Width", cm.dataIndex, cm.width);


}
if (typeof console != "undefined")
console.debug("Grid.buildColumnModel: Created Columns for ", config);
return new Ext.grid.ColumnModel(config);
}


});


Ext.ux.MultiGroupingPagingGrid = Ext.extend(Ext.ux.MultiGroupingGrid, {


/** When creating the store, register an internal callback for post load processing
*/
constructor: function(config) {
config = config||{};
config.bbar = [].concat(config.bbar);
config.bbar = config.bbar.concat([
{xtype:'tbfill'}
,{xtype:'tbtext',id:'counter', text: '? of ?'}
,{xtype:'tbspacer'}
,{xtype:'tbbutton',id:'loading',hidden: true,iconCls: "x-tbar-loading"}
,{xtype:'tbseparator'}
,{xtype:'tbbutton',id:'more',text: '>>',handler: function() { this.store.loadMore(false); }, scope: this}
]);

Ext.ux.MultiGroupingPagingGrid.superclass.constructor.apply(this, arguments);

// Create Event that asks for more data when we scroll to the end
this.on("bodyscroll", function() {
var s = this.view.scroller.dom;
if( (s.offsetHeight+s.scrollTop+5 > s.scrollHeight) && !this.isLoading) {
if (typeof console != "undefined")
console.debug("Grid.on.bodyscroll: Get more...");
this.store.loadMore(false);
}
}, this);

// When the grid start loading, display a loading icon
this.store.on("beforeload", function(store,o) {
if(this.isLoading) {
if (typeof console != "undefined")
console.debug("Store.on.beforeload: Reject Load, one is in progress");
return false;
}
this.isLoading = true;
if(this.rendered) {
this.barLoading.show();
}
if (typeof console != "undefined")
console.debug("Store.on.beforeload: options=",o, this);
return true;
}, this);

// When loading has finished, disable the loading icon, and update the row count
this.store.on("load", function() {
delete this.isLoading;
if(this.rendered) {
this.barLoading.hide();
if (typeof console != "undefined")
console.debug("Store.on.load: Finished loading.. ",this.store.totalCount);
this.barCounter.getEl().innerHTML = "Mostrando " + this.store.getCount()+' de ' +
(this.store.totalCount?this.store.totalCount:'?');
if(this.store.totalCount)
this.barMore.disable();
else
this.barMore.enable();
}
return true;
}, this);


// When a loading error occurs, disable the loading icon and display error
this.store.on("loadexception", function(store, e) {
if (typeof console != "undefined")
console.debug("Store.loadexception.Event:",arguments);
delete this.isLoading;
if(this.rendered) {
this.barLoading.hide();
}
if(e)
Ext.Msg.show({
title:'Show Details',
msg: "Error cargando registros - " + e,
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.ERROR
});
return false;
}, this);


// As the default onLoad to refocus on the first row has been disabled,
// This has been added so if a load does happen, and its an initial load
// it refocuses. If this is a refresh caused by a sort/group or a new page
// of data being loaded, it does not refocus
this.store.on("load", function(r,o) {
if(o&&o.initial==true)
Ext.ux.MultiGroupingView.superclass.onLoad.call(this);
}, this.view);
}

// private
,onRender : function(ct, position){
Ext.ux.MultiGroupingPagingGrid.superclass.onRender.call(this, ct, position);
var bb=this.getBottomToolbar();
this.barCounter = bb.items.itemAt(bb.items.length-5);
this.barMore = bb.items.itemAt(bb.items.length-1);
this.barLoading = bb.items.itemAt(bb.items.length-3);
}
});

yyogev
30 Oct 2011, 10:10 PM
Since this code is not checked in and there are several versions floating around, it would be nicer to get your fixes as a patch.

Thanks,

trvd1707
31 Oct 2011, 5:15 AM
I don't know how to do that.

yyogev
31 Oct 2011, 5:38 AM
In Unix/Linux, a simple "diff -u file1 file2" would produce the required output, where file1 contains the code before the change, and file2 has the code after the change.
In Windows, you can use GNU Diffutils (http://www.gnu.org/software/diffutils/diffutils.html).

tsreenath1985
6 Feb 2012, 3:16 AM
Hi,
I am new to ExtJS. I am using ExtJS 4. I am trying to implement Multigrouping. Could you please provide me a working code of multigrouping which i can run it on my pc. thanks.

yyogev
6 Feb 2012, 3:32 AM
Hi,
I am new to ExtJS. I am using ExtJS 4. I am trying to implement Multigrouping. Could you please provide me a working code of multigrouping which i can run it on my pc. thanks.

Please read the posts above: the multigrouping user extension was written for ExtJS 2. and later fixed so it can work on ExtJS 3. It does not work on ExtJS 4.
If you do decide to take a shot at making it work on ExtJS, may the force be with you!. I would recommend to create a new extension based on the ExtJS 4 Ext.grid.feature.Grouping, rather than trying to fix the existing extension.