1 | /* |
---|
2 | * Ext JS Library 2.2.1 |
---|
3 | * Copyright(c) 2006-2009, Ext JS, LLC. |
---|
4 | * licensing@extjs.com |
---|
5 | * |
---|
6 | * http://extjs.com/license |
---|
7 | */ |
---|
8 | |
---|
9 | /** |
---|
10 | * @class Ext.grid.GroupingView |
---|
11 | * @extends Ext.grid.GridView |
---|
12 | * Adds the ability for single level grouping to the grid. |
---|
13 | *<pre><code>var grid = new Ext.grid.GridPanel({ |
---|
14 | // A groupingStore is required for a GroupingView |
---|
15 | store: new Ext.data.GroupingStore({ |
---|
16 | reader: reader, |
---|
17 | data: xg.dummyData, |
---|
18 | sortInfo:{field: 'company', direction: "ASC"}, |
---|
19 | groupField:'industry' |
---|
20 | }), |
---|
21 | |
---|
22 | columns: [ |
---|
23 | {id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'}, |
---|
24 | {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'}, |
---|
25 | {header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney}, |
---|
26 | {header: "Industry", width: 20, sortable: true, dataIndex: 'industry'}, |
---|
27 | {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'} |
---|
28 | ], |
---|
29 | |
---|
30 | view: new Ext.grid.GroupingView({ |
---|
31 | forceFit:true, |
---|
32 | // custom grouping text template to display the number of items per group |
---|
33 | groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})' |
---|
34 | }), |
---|
35 | |
---|
36 | frame:true, |
---|
37 | width: 700, |
---|
38 | height: 450, |
---|
39 | collapsible: true, |
---|
40 | animCollapse: false, |
---|
41 | title: 'Grouping Example', |
---|
42 | iconCls: 'icon-grid', |
---|
43 | renderTo: document.body |
---|
44 | });</code></pre> |
---|
45 | * @constructor |
---|
46 | * @param {Object} config |
---|
47 | */ |
---|
48 | Ext.grid.GroupingView = Ext.extend(Ext.grid.GridView, { |
---|
49 | /** |
---|
50 | * @cfg {Boolean} hideGroupedColumn True to hide the column that is currently grouped |
---|
51 | */ |
---|
52 | hideGroupedColumn:false, |
---|
53 | /** |
---|
54 | * @cfg {Boolean} showGroupName True to display the name for each set of grouped rows (defaults to true) |
---|
55 | */ |
---|
56 | showGroupName:true, |
---|
57 | /** |
---|
58 | * @cfg {Boolean} startCollapsed True to start all groups collapsed |
---|
59 | */ |
---|
60 | startCollapsed:false, |
---|
61 | /** |
---|
62 | * @cfg {Boolean} enableGrouping False to disable grouping functionality (defaults to true) |
---|
63 | */ |
---|
64 | enableGrouping:true, |
---|
65 | /** |
---|
66 | * @cfg {Boolean} enableGroupingMenu True to enable the grouping control in the column menu |
---|
67 | */ |
---|
68 | enableGroupingMenu:true, |
---|
69 | /** |
---|
70 | * @cfg {Boolean} enableNoGroups True to allow the user to turn off grouping |
---|
71 | */ |
---|
72 | enableNoGroups:true, |
---|
73 | /** |
---|
74 | * @cfg {String} emptyGroupText The text to display when there is an empty group value |
---|
75 | */ |
---|
76 | emptyGroupText : '(None)', |
---|
77 | /** |
---|
78 | * @cfg {Boolean} ignoreAdd True to skip refreshing the view when new rows are added (defaults to false) |
---|
79 | */ |
---|
80 | ignoreAdd: false, |
---|
81 | /** |
---|
82 | * @cfg {String} groupTextTpl The template used to render the group header. This is used to |
---|
83 | * format an object which contains the following properties: |
---|
84 | * <div class="mdetail-params"><ul> |
---|
85 | * <li><b>group</b> : String<p class="sub-desc">The <i>rendered</i> value of the group field. |
---|
86 | * By default this is the unchanged value of the group field. If a {@link #groupRenderer} |
---|
87 | * is specified, it is the result of a call to that.</p></li> |
---|
88 | * <li><b>gvalue</b> : Object<p class="sub-desc">The <i>raw</i> value of the group field.</p></li> |
---|
89 | * <li><b>text</b> : String<p class="sub-desc">The configured {@link #header} (If |
---|
90 | * {@link #showGroupName} is true) plus the <i>rendered</i>group field value.</p></li> |
---|
91 | * <li><b>groupId</b> : String<p class="sub-desc">A unique, generated ID which is applied to the |
---|
92 | * View Element which contains the group.</p></li> |
---|
93 | * <li><b>startRow</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li> |
---|
94 | * <li><b>rs</b> : Array<p class="sub-desc">.Contains a single element: The Record providing the data |
---|
95 | * for the row which caused group change.</p></li> |
---|
96 | * <li><b>cls</b> : String<p class="sub-desc">The generated class name string to apply to the group header Element.</p></li> |
---|
97 | * <li><b>style</b> : String<p class="sub-desc">The inline style rules to apply to the group header Element.</p></li> |
---|
98 | * </ul></div></p> |
---|
99 | * See {@link Ext.XTemplate} for information on how to format data using a template. |
---|
100 | */ |
---|
101 | groupTextTpl : '{text}', |
---|
102 | /** |
---|
103 | * @cfg {Function} groupRenderer The function used to format the grouping field value for |
---|
104 | * display in the group header. Should return a string value. This takes the following parameters: |
---|
105 | * <div class="mdetail-params"><ul> |
---|
106 | * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li> |
---|
107 | * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li> |
---|
108 | * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data |
---|
109 | * for the row which caused group change.</p></li> |
---|
110 | * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li> |
---|
111 | * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li> |
---|
112 | * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li> |
---|
113 | * </ul></div></p> |
---|
114 | */ |
---|
115 | /** |
---|
116 | * @cfg {String} header The text with which to prefix the group field value in the group header line. |
---|
117 | */ |
---|
118 | |
---|
119 | // private |
---|
120 | gidSeed : 1000, |
---|
121 | |
---|
122 | // private |
---|
123 | initTemplates : function(){ |
---|
124 | Ext.grid.GroupingView.superclass.initTemplates.call(this); |
---|
125 | this.state = {}; |
---|
126 | |
---|
127 | var sm = this.grid.getSelectionModel(); |
---|
128 | sm.on(sm.selectRow ? 'beforerowselect' : 'beforecellselect', |
---|
129 | this.onBeforeRowSelect, this); |
---|
130 | |
---|
131 | if(!this.startGroup){ |
---|
132 | this.startGroup = new Ext.XTemplate( |
---|
133 | '<div id="{groupId}" class="x-grid-group {cls}">', |
---|
134 | '<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div>', this.groupTextTpl ,'</div></div>', |
---|
135 | '<div id="{groupId}-bd" class="x-grid-group-body">' |
---|
136 | ); |
---|
137 | } |
---|
138 | this.startGroup.compile(); |
---|
139 | this.endGroup = '</div></div>'; |
---|
140 | }, |
---|
141 | |
---|
142 | // private |
---|
143 | findGroup : function(el){ |
---|
144 | return Ext.fly(el).up('.x-grid-group', this.mainBody.dom); |
---|
145 | }, |
---|
146 | |
---|
147 | // private |
---|
148 | getGroups : function(){ |
---|
149 | return this.hasRows() ? this.mainBody.dom.childNodes : []; |
---|
150 | }, |
---|
151 | |
---|
152 | // private |
---|
153 | onAdd : function(){ |
---|
154 | if(this.enableGrouping && !this.ignoreAdd){ |
---|
155 | var ss = this.getScrollState(); |
---|
156 | this.refresh(); |
---|
157 | this.restoreScroll(ss); |
---|
158 | }else if(!this.enableGrouping){ |
---|
159 | Ext.grid.GroupingView.superclass.onAdd.apply(this, arguments); |
---|
160 | } |
---|
161 | }, |
---|
162 | |
---|
163 | // private |
---|
164 | onRemove : function(ds, record, index, isUpdate){ |
---|
165 | Ext.grid.GroupingView.superclass.onRemove.apply(this, arguments); |
---|
166 | var g = document.getElementById(record._groupId); |
---|
167 | if(g && g.childNodes[1].childNodes.length < 1){ |
---|
168 | Ext.removeNode(g); |
---|
169 | } |
---|
170 | this.applyEmptyText(); |
---|
171 | }, |
---|
172 | |
---|
173 | // private |
---|
174 | refreshRow : function(record){ |
---|
175 | if(this.ds.getCount()==1){ |
---|
176 | this.refresh(); |
---|
177 | }else{ |
---|
178 | this.isUpdating = true; |
---|
179 | Ext.grid.GroupingView.superclass.refreshRow.apply(this, arguments); |
---|
180 | this.isUpdating = false; |
---|
181 | } |
---|
182 | }, |
---|
183 | |
---|
184 | // private |
---|
185 | beforeMenuShow : function(){ |
---|
186 | var field = this.getGroupField(); |
---|
187 | var g = this.hmenu.items.get('groupBy'); |
---|
188 | if(g){ |
---|
189 | g.setDisabled(this.cm.config[this.hdCtxIndex].groupable === false); |
---|
190 | } |
---|
191 | var s = this.hmenu.items.get('showGroups'); |
---|
192 | if(s){ |
---|
193 | s.setDisabled(!field && this.cm.config[this.hdCtxIndex].groupable === false); |
---|
194 | s.setChecked(!!field, true); |
---|
195 | } |
---|
196 | }, |
---|
197 | |
---|
198 | // private |
---|
199 | renderUI : function(){ |
---|
200 | Ext.grid.GroupingView.superclass.renderUI.call(this); |
---|
201 | this.mainBody.on('mousedown', this.interceptMouse, this); |
---|
202 | |
---|
203 | if(this.enableGroupingMenu && this.hmenu){ |
---|
204 | this.hmenu.add('-',{ |
---|
205 | id:'groupBy', |
---|
206 | text: this.groupByText, |
---|
207 | handler: this.onGroupByClick, |
---|
208 | scope: this, |
---|
209 | iconCls:'x-group-by-icon' |
---|
210 | }); |
---|
211 | if(this.enableNoGroups){ |
---|
212 | this.hmenu.add({ |
---|
213 | id:'showGroups', |
---|
214 | text: this.showGroupsText, |
---|
215 | checked: true, |
---|
216 | checkHandler: this.onShowGroupsClick, |
---|
217 | scope: this |
---|
218 | }); |
---|
219 | } |
---|
220 | this.hmenu.on('beforeshow', this.beforeMenuShow, this); |
---|
221 | } |
---|
222 | }, |
---|
223 | |
---|
224 | // private |
---|
225 | onGroupByClick : function(){ |
---|
226 | this.grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex)); |
---|
227 | this.beforeMenuShow(); // Make sure the checkboxes get properly set when changing groups |
---|
228 | }, |
---|
229 | |
---|
230 | // private |
---|
231 | onShowGroupsClick : function(mi, checked){ |
---|
232 | if(checked){ |
---|
233 | this.onGroupByClick(); |
---|
234 | }else{ |
---|
235 | this.grid.store.clearGrouping(); |
---|
236 | } |
---|
237 | }, |
---|
238 | |
---|
239 | /** |
---|
240 | * Toggles the specified group if no value is passed, otherwise sets the expanded state of the group to the value passed. |
---|
241 | * @param {String} groupId The groupId assigned to the group (see getGroupId) |
---|
242 | * @param {Boolean} expanded (optional) |
---|
243 | */ |
---|
244 | toggleGroup : function(group, expanded){ |
---|
245 | this.grid.stopEditing(true); |
---|
246 | group = Ext.getDom(group); |
---|
247 | var gel = Ext.fly(group); |
---|
248 | expanded = expanded !== undefined ? |
---|
249 | expanded : gel.hasClass('x-grid-group-collapsed'); |
---|
250 | |
---|
251 | this.state[gel.dom.id] = expanded; |
---|
252 | gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed'); |
---|
253 | }, |
---|
254 | |
---|
255 | /** |
---|
256 | * Toggles all groups if no value is passed, otherwise sets the expanded state of all groups to the value passed. |
---|
257 | * @param {Boolean} expanded (optional) |
---|
258 | */ |
---|
259 | toggleAllGroups : function(expanded){ |
---|
260 | var groups = this.getGroups(); |
---|
261 | for(var i = 0, len = groups.length; i < len; i++){ |
---|
262 | this.toggleGroup(groups[i], expanded); |
---|
263 | } |
---|
264 | }, |
---|
265 | |
---|
266 | /** |
---|
267 | * Expands all grouped rows. |
---|
268 | */ |
---|
269 | expandAllGroups : function(){ |
---|
270 | this.toggleAllGroups(true); |
---|
271 | }, |
---|
272 | |
---|
273 | /** |
---|
274 | * Collapses all grouped rows. |
---|
275 | */ |
---|
276 | collapseAllGroups : function(){ |
---|
277 | this.toggleAllGroups(false); |
---|
278 | }, |
---|
279 | |
---|
280 | // private |
---|
281 | interceptMouse : function(e){ |
---|
282 | var hd = e.getTarget('.x-grid-group-hd', this.mainBody); |
---|
283 | if(hd){ |
---|
284 | e.stopEvent(); |
---|
285 | this.toggleGroup(hd.parentNode); |
---|
286 | } |
---|
287 | }, |
---|
288 | |
---|
289 | // private |
---|
290 | getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){ |
---|
291 | var g = groupRenderer ? groupRenderer(v, {}, r, rowIndex, colIndex, ds) : String(v); |
---|
292 | if(g === ''){ |
---|
293 | g = this.cm.config[colIndex].emptyGroupText || this.emptyGroupText; |
---|
294 | } |
---|
295 | return g; |
---|
296 | }, |
---|
297 | |
---|
298 | // private |
---|
299 | getGroupField : function(){ |
---|
300 | return this.grid.store.getGroupState(); |
---|
301 | }, |
---|
302 | |
---|
303 | // private |
---|
304 | renderRows : function(){ |
---|
305 | var groupField = this.getGroupField(); |
---|
306 | var eg = !!groupField; |
---|
307 | // if they turned off grouping and the last grouped field is hidden |
---|
308 | if(this.hideGroupedColumn) { |
---|
309 | var colIndex = this.cm.findColumnIndex(groupField); |
---|
310 | if(!eg && this.lastGroupField !== undefined) { |
---|
311 | this.mainBody.update(''); |
---|
312 | this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField), false); |
---|
313 | delete this.lastGroupField; |
---|
314 | }else if (eg && this.lastGroupField === undefined) { |
---|
315 | this.lastGroupField = groupField; |
---|
316 | this.cm.setHidden(colIndex, true); |
---|
317 | }else if (eg && this.lastGroupField !== undefined && groupField !== this.lastGroupField) { |
---|
318 | this.mainBody.update(''); |
---|
319 | var oldIndex = this.cm.findColumnIndex(this.lastGroupField); |
---|
320 | this.cm.setHidden(oldIndex, false); |
---|
321 | this.lastGroupField = groupField; |
---|
322 | this.cm.setHidden(colIndex, true); |
---|
323 | } |
---|
324 | } |
---|
325 | return Ext.grid.GroupingView.superclass.renderRows.apply( |
---|
326 | this, arguments); |
---|
327 | }, |
---|
328 | |
---|
329 | // private |
---|
330 | doRender : function(cs, rs, ds, startRow, colCount, stripe){ |
---|
331 | if(rs.length < 1){ |
---|
332 | return ''; |
---|
333 | } |
---|
334 | var groupField = this.getGroupField(); |
---|
335 | var colIndex = this.cm.findColumnIndex(groupField); |
---|
336 | |
---|
337 | this.enableGrouping = !!groupField; |
---|
338 | |
---|
339 | if(!this.enableGrouping || this.isUpdating){ |
---|
340 | return Ext.grid.GroupingView.superclass.doRender.apply( |
---|
341 | this, arguments); |
---|
342 | } |
---|
343 | var gstyle = 'width:'+this.getTotalWidth()+';'; |
---|
344 | |
---|
345 | var gidPrefix = this.grid.getGridEl().id; |
---|
346 | var cfg = this.cm.config[colIndex]; |
---|
347 | var groupRenderer = cfg.groupRenderer || cfg.renderer; |
---|
348 | var prefix = this.showGroupName ? |
---|
349 | (cfg.groupName || cfg.header)+': ' : ''; |
---|
350 | |
---|
351 | var groups = [], curGroup, i, len, gid; |
---|
352 | for(i = 0, len = rs.length; i < len; i++){ |
---|
353 | var rowIndex = startRow + i; |
---|
354 | var r = rs[i], |
---|
355 | gvalue = r.data[groupField], |
---|
356 | g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds); |
---|
357 | if(!curGroup || curGroup.group != g){ |
---|
358 | gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g); |
---|
359 | // if state is defined use it, however state is in terms of expanded |
---|
360 | // so negate it, otherwise use the default. |
---|
361 | var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed; |
---|
362 | var gcls = isCollapsed ? 'x-grid-group-collapsed' : ''; |
---|
363 | curGroup = { |
---|
364 | group: g, |
---|
365 | gvalue: gvalue, |
---|
366 | text: prefix + g, |
---|
367 | groupId: gid, |
---|
368 | startRow: rowIndex, |
---|
369 | rs: [r], |
---|
370 | cls: gcls, |
---|
371 | style: gstyle |
---|
372 | }; |
---|
373 | groups.push(curGroup); |
---|
374 | }else{ |
---|
375 | curGroup.rs.push(r); |
---|
376 | } |
---|
377 | r._groupId = gid; |
---|
378 | } |
---|
379 | |
---|
380 | var buf = []; |
---|
381 | for(i = 0, len = groups.length; i < len; i++){ |
---|
382 | var g = groups[i]; |
---|
383 | this.doGroupStart(buf, g, cs, ds, colCount); |
---|
384 | buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call( |
---|
385 | this, cs, g.rs, ds, g.startRow, colCount, stripe); |
---|
386 | |
---|
387 | this.doGroupEnd(buf, g, cs, ds, colCount); |
---|
388 | } |
---|
389 | return buf.join(''); |
---|
390 | }, |
---|
391 | |
---|
392 | /** |
---|
393 | * Dynamically tries to determine the groupId of a specific value |
---|
394 | * @param {String} value |
---|
395 | * @return {String} The group id |
---|
396 | */ |
---|
397 | getGroupId : function(value){ |
---|
398 | var gidPrefix = this.grid.getGridEl().id; |
---|
399 | var groupField = this.getGroupField(); |
---|
400 | var colIndex = this.cm.findColumnIndex(groupField); |
---|
401 | var cfg = this.cm.config[colIndex]; |
---|
402 | var groupRenderer = cfg.groupRenderer || cfg.renderer; |
---|
403 | var gtext = this.getGroup(value, {data:{}}, groupRenderer, 0, colIndex, this.ds); |
---|
404 | return gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(value); |
---|
405 | }, |
---|
406 | |
---|
407 | // private |
---|
408 | doGroupStart : function(buf, g, cs, ds, colCount){ |
---|
409 | buf[buf.length] = this.startGroup.apply(g); |
---|
410 | }, |
---|
411 | |
---|
412 | // private |
---|
413 | doGroupEnd : function(buf, g, cs, ds, colCount){ |
---|
414 | buf[buf.length] = this.endGroup; |
---|
415 | }, |
---|
416 | |
---|
417 | // private |
---|
418 | getRows : function(){ |
---|
419 | if(!this.enableGrouping){ |
---|
420 | return Ext.grid.GroupingView.superclass.getRows.call(this); |
---|
421 | } |
---|
422 | var r = []; |
---|
423 | var g, gs = this.getGroups(); |
---|
424 | for(var i = 0, len = gs.length; i < len; i++){ |
---|
425 | g = gs[i].childNodes[1].childNodes; |
---|
426 | for(var j = 0, jlen = g.length; j < jlen; j++){ |
---|
427 | r[r.length] = g[j]; |
---|
428 | } |
---|
429 | } |
---|
430 | return r; |
---|
431 | }, |
---|
432 | |
---|
433 | // private |
---|
434 | updateGroupWidths : function(){ |
---|
435 | if(!this.enableGrouping || !this.hasRows()){ |
---|
436 | return; |
---|
437 | } |
---|
438 | var tw = Math.max(this.cm.getTotalWidth(), this.el.dom.offsetWidth-this.scrollOffset) +'px'; |
---|
439 | var gs = this.getGroups(); |
---|
440 | for(var i = 0, len = gs.length; i < len; i++){ |
---|
441 | gs[i].firstChild.style.width = tw; |
---|
442 | } |
---|
443 | }, |
---|
444 | |
---|
445 | // private |
---|
446 | onColumnWidthUpdated : function(col, w, tw){ |
---|
447 | Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this, col, w, tw); |
---|
448 | this.updateGroupWidths(); |
---|
449 | }, |
---|
450 | |
---|
451 | // private |
---|
452 | onAllColumnWidthsUpdated : function(ws, tw){ |
---|
453 | Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this, ws, tw); |
---|
454 | this.updateGroupWidths(); |
---|
455 | }, |
---|
456 | |
---|
457 | // private |
---|
458 | onColumnHiddenUpdated : function(col, hidden, tw){ |
---|
459 | Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this, col, hidden, tw); |
---|
460 | this.updateGroupWidths(); |
---|
461 | }, |
---|
462 | |
---|
463 | // private |
---|
464 | onLayout : function(){ |
---|
465 | this.updateGroupWidths(); |
---|
466 | }, |
---|
467 | |
---|
468 | // private |
---|
469 | onBeforeRowSelect : function(sm, rowIndex){ |
---|
470 | if(!this.enableGrouping){ |
---|
471 | return; |
---|
472 | } |
---|
473 | var row = this.getRow(rowIndex); |
---|
474 | if(row && !row.offsetParent){ |
---|
475 | var g = this.findGroup(row); |
---|
476 | this.toggleGroup(g, true); |
---|
477 | } |
---|
478 | }, |
---|
479 | |
---|
480 | /** |
---|
481 | * @cfg {String} groupByText Text displayed in the grid header menu for grouping by a column |
---|
482 | * (defaults to 'Group By This Field'). |
---|
483 | */ |
---|
484 | groupByText: 'Group By This Field', |
---|
485 | /** |
---|
486 | * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping |
---|
487 | * (defaults to 'Show in Groups'). |
---|
488 | */ |
---|
489 | showGroupsText: 'Show in Groups' |
---|
490 | }); |
---|
491 | // private |
---|
492 | Ext.grid.GroupingView.GROUP_ID = 1000; |
---|