2 * Ext JS Library 2.2.1
\r
3 * Copyright(c) 2006-2009, Ext JS, LLC.
\r
4 * licensing@extjs.com
\r
6 * http://extjs.com/license
\r
10 * @class Ext.grid.GroupingView
\r
11 * @extends Ext.grid.GridView
\r
12 * Adds the ability for single level grouping to the grid.
\r
13 *<pre><code>var grid = new Ext.grid.GridPanel({
\r
14 // A groupingStore is required for a GroupingView
\r
15 store: new Ext.data.GroupingStore({
\r
18 sortInfo:{field: 'company', direction: "ASC"},
\r
19 groupField:'industry'
\r
23 {id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'},
\r
24 {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
\r
25 {header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
\r
26 {header: "Industry", width: 20, sortable: true, dataIndex: 'industry'},
\r
27 {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
\r
30 view: new Ext.grid.GroupingView({
\r
32 // custom grouping text template to display the number of items per group
\r
33 groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
\r
40 animCollapse: false,
\r
41 title: 'Grouping Example',
\r
42 iconCls: 'icon-grid',
\r
43 renderTo: document.body
\r
46 * @param {Object} config
\r
48 Ext.grid.GroupingView = Ext.extend(Ext.grid.GridView, {
\r
50 * @cfg {Boolean} hideGroupedColumn True to hide the column that is currently grouped
\r
52 hideGroupedColumn:false,
\r
54 * @cfg {Boolean} showGroupName True to display the name for each set of grouped rows (defaults to true)
\r
58 * @cfg {Boolean} startCollapsed True to start all groups collapsed
\r
60 startCollapsed:false,
\r
62 * @cfg {Boolean} enableGrouping False to disable grouping functionality (defaults to true)
\r
64 enableGrouping:true,
\r
66 * @cfg {Boolean} enableGroupingMenu True to enable the grouping control in the column menu
\r
68 enableGroupingMenu:true,
\r
70 * @cfg {Boolean} enableNoGroups True to allow the user to turn off grouping
\r
72 enableNoGroups:true,
\r
74 * @cfg {String} emptyGroupText The text to display when there is an empty group value
\r
76 emptyGroupText : '(None)',
\r
78 * @cfg {Boolean} ignoreAdd True to skip refreshing the view when new rows are added (defaults to false)
\r
82 * @cfg {String} groupTextTpl The template used to render the group header. This is used to
\r
83 * format an object which contains the following properties:
\r
84 * <div class="mdetail-params"><ul>
\r
85 * <li><b>group</b> : String<p class="sub-desc">The <i>rendered</i> value of the group field.
\r
86 * By default this is the unchanged value of the group field. If a {@link #groupRenderer}
\r
87 * is specified, it is the result of a call to that.</p></li>
\r
88 * <li><b>gvalue</b> : Object<p class="sub-desc">The <i>raw</i> value of the group field.</p></li>
\r
89 * <li><b>text</b> : String<p class="sub-desc">The configured {@link #header} (If
\r
90 * {@link #showGroupName} is true) plus the <i>rendered</i>group field value.</p></li>
\r
91 * <li><b>groupId</b> : String<p class="sub-desc">A unique, generated ID which is applied to the
\r
92 * View Element which contains the group.</p></li>
\r
93 * <li><b>startRow</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
94 * <li><b>rs</b> : Array<p class="sub-desc">.Contains a single element: The Record providing the data
\r
95 * for the row which caused group change.</p></li>
\r
96 * <li><b>cls</b> : String<p class="sub-desc">The generated class name string to apply to the group header Element.</p></li>
\r
97 * <li><b>style</b> : String<p class="sub-desc">The inline style rules to apply to the group header Element.</p></li>
\r
99 * See {@link Ext.XTemplate} for information on how to format data using a template.
\r
101 groupTextTpl : '{text}',
\r
103 * @cfg {Function} groupRenderer The function used to format the grouping field value for
\r
104 * display in the group header. Should return a string value. This takes the following parameters:
\r
105 * <div class="mdetail-params"><ul>
\r
106 * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li>
\r
107 * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li>
\r
108 * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data
\r
109 * for the row which caused group change.</p></li>
\r
110 * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
111 * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li>
\r
112 * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
\r
116 * @cfg {String} header The text with which to prefix the group field value in the group header line.
\r
123 initTemplates : function(){
\r
124 Ext.grid.GroupingView.superclass.initTemplates.call(this);
\r
127 var sm = this.grid.getSelectionModel();
\r
128 sm.on(sm.selectRow ? 'beforerowselect' : 'beforecellselect',
\r
129 this.onBeforeRowSelect, this);
\r
131 if(!this.startGroup){
\r
132 this.startGroup = new Ext.XTemplate(
\r
133 '<div id="{groupId}" class="x-grid-group {cls}">',
\r
134 '<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div>', this.groupTextTpl ,'</div></div>',
\r
135 '<div id="{groupId}-bd" class="x-grid-group-body">'
\r
138 this.startGroup.compile();
\r
139 this.endGroup = '</div></div>';
\r
143 findGroup : function(el){
\r
144 return Ext.fly(el).up('.x-grid-group', this.mainBody.dom);
\r
148 getGroups : function(){
\r
149 return this.hasRows() ? this.mainBody.dom.childNodes : [];
\r
153 onAdd : function(){
\r
154 if(this.enableGrouping && !this.ignoreAdd){
\r
155 var ss = this.getScrollState();
\r
157 this.restoreScroll(ss);
\r
158 }else if(!this.enableGrouping){
\r
159 Ext.grid.GroupingView.superclass.onAdd.apply(this, arguments);
\r
164 onRemove : function(ds, record, index, isUpdate){
\r
165 Ext.grid.GroupingView.superclass.onRemove.apply(this, arguments);
\r
166 var g = document.getElementById(record._groupId);
\r
167 if(g && g.childNodes[1].childNodes.length < 1){
\r
170 this.applyEmptyText();
\r
174 refreshRow : function(record){
\r
175 if(this.ds.getCount()==1){
\r
178 this.isUpdating = true;
\r
179 Ext.grid.GroupingView.superclass.refreshRow.apply(this, arguments);
\r
180 this.isUpdating = false;
\r
185 beforeMenuShow : function(){
\r
186 var field = this.getGroupField();
\r
187 var g = this.hmenu.items.get('groupBy');
\r
189 g.setDisabled(this.cm.config[this.hdCtxIndex].groupable === false);
\r
191 var s = this.hmenu.items.get('showGroups');
\r
193 s.setDisabled(!field && this.cm.config[this.hdCtxIndex].groupable === false);
\r
194 s.setChecked(!!field, true);
\r
199 renderUI : function(){
\r
200 Ext.grid.GroupingView.superclass.renderUI.call(this);
\r
201 this.mainBody.on('mousedown', this.interceptMouse, this);
\r
203 if(this.enableGroupingMenu && this.hmenu){
\r
204 this.hmenu.add('-',{
\r
206 text: this.groupByText,
\r
207 handler: this.onGroupByClick,
\r
209 iconCls:'x-group-by-icon'
\r
211 if(this.enableNoGroups){
\r
214 text: this.showGroupsText,
\r
216 checkHandler: this.onShowGroupsClick,
\r
220 this.hmenu.on('beforeshow', this.beforeMenuShow, this);
\r
225 onGroupByClick : function(){
\r
226 this.grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
\r
227 this.beforeMenuShow(); // Make sure the checkboxes get properly set when changing groups
\r
231 onShowGroupsClick : function(mi, checked){
\r
233 this.onGroupByClick();
\r
235 this.grid.store.clearGrouping();
\r
240 * Toggles the specified group if no value is passed, otherwise sets the expanded state of the group to the value passed.
\r
241 * @param {String} groupId The groupId assigned to the group (see getGroupId)
\r
242 * @param {Boolean} expanded (optional)
\r
244 toggleGroup : function(group, expanded){
\r
245 this.grid.stopEditing(true);
\r
246 group = Ext.getDom(group);
\r
247 var gel = Ext.fly(group);
\r
248 expanded = expanded !== undefined ?
\r
249 expanded : gel.hasClass('x-grid-group-collapsed');
\r
251 this.state[gel.dom.id] = expanded;
\r
252 gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed');
\r
256 * Toggles all groups if no value is passed, otherwise sets the expanded state of all groups to the value passed.
\r
257 * @param {Boolean} expanded (optional)
\r
259 toggleAllGroups : function(expanded){
\r
260 var groups = this.getGroups();
\r
261 for(var i = 0, len = groups.length; i < len; i++){
\r
262 this.toggleGroup(groups[i], expanded);
\r
267 * Expands all grouped rows.
\r
269 expandAllGroups : function(){
\r
270 this.toggleAllGroups(true);
\r
274 * Collapses all grouped rows.
\r
276 collapseAllGroups : function(){
\r
277 this.toggleAllGroups(false);
\r
281 interceptMouse : function(e){
\r
282 var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
\r
285 this.toggleGroup(hd.parentNode);
\r
290 getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){
\r
291 var g = groupRenderer ? groupRenderer(v, {}, r, rowIndex, colIndex, ds) : String(v);
\r
293 g = this.cm.config[colIndex].emptyGroupText || this.emptyGroupText;
\r
299 getGroupField : function(){
\r
300 return this.grid.store.getGroupState();
\r
304 renderRows : function(){
\r
305 var groupField = this.getGroupField();
\r
306 var eg = !!groupField;
\r
307 // if they turned off grouping and the last grouped field is hidden
\r
308 if(this.hideGroupedColumn) {
\r
309 var colIndex = this.cm.findColumnIndex(groupField);
\r
310 if(!eg && this.lastGroupField !== undefined) {
\r
311 this.mainBody.update('');
\r
312 this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField), false);
\r
313 delete this.lastGroupField;
\r
314 }else if (eg && this.lastGroupField === undefined) {
\r
315 this.lastGroupField = groupField;
\r
316 this.cm.setHidden(colIndex, true);
\r
317 }else if (eg && this.lastGroupField !== undefined && groupField !== this.lastGroupField) {
\r
318 this.mainBody.update('');
\r
319 var oldIndex = this.cm.findColumnIndex(this.lastGroupField);
\r
320 this.cm.setHidden(oldIndex, false);
\r
321 this.lastGroupField = groupField;
\r
322 this.cm.setHidden(colIndex, true);
\r
325 return Ext.grid.GroupingView.superclass.renderRows.apply(
\r
330 doRender : function(cs, rs, ds, startRow, colCount, stripe){
\r
334 var groupField = this.getGroupField();
\r
335 var colIndex = this.cm.findColumnIndex(groupField);
\r
337 this.enableGrouping = !!groupField;
\r
339 if(!this.enableGrouping || this.isUpdating){
\r
340 return Ext.grid.GroupingView.superclass.doRender.apply(
\r
343 var gstyle = 'width:'+this.getTotalWidth()+';';
\r
345 var gidPrefix = this.grid.getGridEl().id;
\r
346 var cfg = this.cm.config[colIndex];
\r
347 var groupRenderer = cfg.groupRenderer || cfg.renderer;
\r
348 var prefix = this.showGroupName ?
\r
349 (cfg.groupName || cfg.header)+': ' : '';
\r
351 var groups = [], curGroup, i, len, gid;
\r
352 for(i = 0, len = rs.length; i < len; i++){
\r
353 var rowIndex = startRow + i;
\r
355 gvalue = r.data[groupField],
\r
356 g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
\r
357 if(!curGroup || curGroup.group != g){
\r
358 gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
\r
359 // if state is defined use it, however state is in terms of expanded
\r
360 // so negate it, otherwise use the default.
\r
361 var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
\r
362 var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
\r
368 startRow: rowIndex,
\r
373 groups.push(curGroup);
\r
375 curGroup.rs.push(r);
\r
381 for(i = 0, len = groups.length; i < len; i++){
\r
383 this.doGroupStart(buf, g, cs, ds, colCount);
\r
384 buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call(
\r
385 this, cs, g.rs, ds, g.startRow, colCount, stripe);
\r
387 this.doGroupEnd(buf, g, cs, ds, colCount);
\r
389 return buf.join('');
\r
393 * Dynamically tries to determine the groupId of a specific value
\r
394 * @param {String} value
\r
395 * @return {String} The group id
\r
397 getGroupId : function(value){
\r
398 var gidPrefix = this.grid.getGridEl().id;
\r
399 var groupField = this.getGroupField();
\r
400 var colIndex = this.cm.findColumnIndex(groupField);
\r
401 var cfg = this.cm.config[colIndex];
\r
402 var groupRenderer = cfg.groupRenderer || cfg.renderer;
\r
403 var gtext = this.getGroup(value, {data:{}}, groupRenderer, 0, colIndex, this.ds);
\r
404 return gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(value);
\r
408 doGroupStart : function(buf, g, cs, ds, colCount){
\r
409 buf[buf.length] = this.startGroup.apply(g);
\r
413 doGroupEnd : function(buf, g, cs, ds, colCount){
\r
414 buf[buf.length] = this.endGroup;
\r
418 getRows : function(){
\r
419 if(!this.enableGrouping){
\r
420 return Ext.grid.GroupingView.superclass.getRows.call(this);
\r
423 var g, gs = this.getGroups();
\r
424 for(var i = 0, len = gs.length; i < len; i++){
\r
425 g = gs[i].childNodes[1].childNodes;
\r
426 for(var j = 0, jlen = g.length; j < jlen; j++){
\r
427 r[r.length] = g[j];
\r
434 updateGroupWidths : function(){
\r
435 if(!this.enableGrouping || !this.hasRows()){
\r
438 var tw = Math.max(this.cm.getTotalWidth(), this.el.dom.offsetWidth-this.scrollOffset) +'px';
\r
439 var gs = this.getGroups();
\r
440 for(var i = 0, len = gs.length; i < len; i++){
\r
441 gs[i].firstChild.style.width = tw;
\r
446 onColumnWidthUpdated : function(col, w, tw){
\r
447 Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this, col, w, tw);
\r
448 this.updateGroupWidths();
\r
452 onAllColumnWidthsUpdated : function(ws, tw){
\r
453 Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this, ws, tw);
\r
454 this.updateGroupWidths();
\r
458 onColumnHiddenUpdated : function(col, hidden, tw){
\r
459 Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this, col, hidden, tw);
\r
460 this.updateGroupWidths();
\r
464 onLayout : function(){
\r
465 this.updateGroupWidths();
\r
469 onBeforeRowSelect : function(sm, rowIndex){
\r
470 if(!this.enableGrouping){
\r
473 var row = this.getRow(rowIndex);
\r
474 if(row && !row.offsetParent){
\r
475 var g = this.findGroup(row);
\r
476 this.toggleGroup(g, true);
\r
481 * @cfg {String} groupByText Text displayed in the grid header menu for grouping by a column
\r
482 * (defaults to 'Group By This Field').
\r
484 groupByText: 'Group By This Field',
\r
486 * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
\r
487 * (defaults to 'Show in Groups').
\r
489 showGroupsText: 'Show in Groups'
\r
492 Ext.grid.GroupingView.GROUP_ID = 1000;