3 * Copyright(c) 2006-2010 Sencha Inc.
5 * http://www.sencha.com/license
8 * @class Ext.grid.PivotAxis
9 * @extends Ext.Component
10 * <p>PivotAxis is a class that supports a {@link Ext.grid.PivotGrid}. Each PivotGrid contains two PivotAxis instances - the left
11 * axis and the top axis. Each PivotAxis defines an ordered set of dimensions, each of which should correspond to a field in a
12 * Store's Record (see {@link Ext.grid.PivotGrid} documentation for further explanation).</p>
13 * <p>Developers should have little interaction with the PivotAxis instances directly as most of their management is performed by
14 * the PivotGrid. An exception is the dynamic reconfiguration of axes at run time - to achieve this we use PivotAxis's
15 * {@link #setDimensions} function and refresh the grid:</p>
17 var pivotGrid = new Ext.grid.PivotGrid({
18 //some PivotGrid config here
21 //change the left axis dimensions
22 pivotGrid.leftAxis.setDimensions([
35 pivotGrid.view.refresh(true);
37 * This clears the previous dimensions on the axis and redraws the grid with the new dimensions.
39 Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
41 * @cfg {String} orientation One of 'vertical' or 'horizontal'. Defaults to horizontal
43 orientation: 'horizontal',
46 * @cfg {Number} defaultHeaderWidth The width to render each row header that does not have a width specified via
47 {@link #getRowGroupHeaders}. Defaults to 80.
49 defaultHeaderWidth: 80,
53 * @cfg {Number} paddingWidth The amount of padding used by each cell.
54 * TODO: From 4.x onwards this can be removed as it won't be needed. For now it is used to account for the differences between
55 * the content box and border box measurement models
60 * Updates the dimensions used by this axis
61 * @param {Array} dimensions The new dimensions
63 setDimensions: function(dimensions) {
64 this.dimensions = dimensions;
69 * Builds the html table that contains the dimensions for this axis. This branches internally between vertical
70 * and horizontal orientations because the table structure is slightly different in each case
72 onRender: function(ct, position) {
73 var rows = this.orientation == 'horizontal'
74 ? this.renderHorizontalRows()
75 : this.renderVerticalRows();
77 this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
82 * Specialised renderer for horizontal oriented axes
83 * @return {Object} The HTML Domspec for a horizontal oriented axis
85 renderHorizontalRows: function() {
86 var headers = this.buildHeaders(),
87 rowCount = headers.length,
89 cells, cols, colCount, i, j;
91 for (i = 0; i < rowCount; i++) {
93 cols = headers[i].items;
94 colCount = cols.length;
96 for (j = 0; j < colCount; j++) {
100 colspan: cols[j].span
115 * Specialised renderer for vertical oriented axes
116 * @return {Object} The HTML Domspec for a vertical oriented axis
118 renderVerticalRows: function() {
119 var headers = this.buildHeaders(),
120 colCount = headers.length,
123 rowCount, col, row, colWidth, i, j;
125 for (i = 0; i < colCount; i++) {
127 colWidth = col.width || 80;
128 rowCount = col.items.length;
130 for (j = 0; j < rowCount; j++) {
133 rowCells[row.start] = rowCells[row.start] || [];
134 rowCells[row.start].push({
138 width : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
143 rowCount = rowCells.length;
144 for (i = 0; i < rowCount; i++) {
156 * Returns the set of all unique tuples based on the bound store and dimension definitions.
157 * Internally we construct a new, temporary store to make use of the multi-sort capabilities of Store. In
158 * 4.x this functionality should have been moved to MixedCollection so this step should not be needed.
159 * @return {Array} All unique tuples
161 getTuples: function() {
162 var newStore = new Ext.data.Store({});
164 newStore.data = this.store.data.clone();
165 newStore.fields = this.store.fields;
168 dimensions = this.dimensions,
169 length = dimensions.length,
172 for (i = 0; i < length; i++) {
174 field : dimensions[i].dataIndex,
175 direction: dimensions[i].direction || 'ASC'
179 newStore.sort(sorters);
181 var records = newStore.data.items,
184 recData, hash, info, data, key;
186 length = records.length;
188 for (i = 0; i < length; i++) {
189 info = this.getRecordInfo(records[i]);
194 hash += data[key] + '---';
197 if (hashes.indexOf(hash) == -1) {
211 getRecordInfo: function(record) {
212 var dimensions = this.dimensions,
213 length = dimensions.length,
215 dimension, dataIndex, i;
217 //get an object containing just the data we are interested in based on the configured dimensions
218 for (i = 0; i < length; i++) {
219 dimension = dimensions[i];
220 dataIndex = dimension.dataIndex;
222 data[dataIndex] = record.get(dataIndex);
225 //creates a specialised matcher function for a given tuple. The returned function will return
226 //true if the record passed to it matches the dataIndex values of each dimension in this axis
227 var createMatcherFunction = function(data) {
228 return function(record) {
229 for (var dataIndex in data) {
230 if (record.get(dataIndex) != data[dataIndex]) {
241 matcher: createMatcherFunction(data)
247 * Uses the calculated set of tuples to build an array of headers that can be rendered into a table using rowspan or
248 * colspan. Basically this takes the set of tuples and spans any cells that run into one another, so if we had dimensions
249 * of Person and Product and several tuples containing different Products for the same Person, those Products would be
251 * @return {Array} The headers
253 buildHeaders: function() {
254 var tuples = this.getTuples(),
255 rowCount = tuples.length,
256 dimensions = this.dimensions,
257 colCount = dimensions.length,
259 tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
261 for (i = 0; i < colCount; i++) {
262 dimension = dimensions[i];
267 for (j = 0; j < rowCount; j++) {
269 isLast = j == (rowCount - 1);
270 currentHeader = tuple.data[dimension.dataIndex];
273 * 'changed' indicates that we need to create a new cell. This should be true whenever the cell
274 * above (previousHeader) is different from this cell, or when the cell on the previous dimension
275 * changed (e.g. if the current dimension is Product and the previous was Person, we need to start
276 * a new cell if Product is the same but Person changed, so we check the previous dimension and tuple)
278 changed = previousHeader != undefined && previousHeader != currentHeader;
279 if (i > 0 && j > 0) {
280 changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
285 header: previousHeader,
296 header: currentHeader,
305 previousHeader = currentHeader;
311 width: dimension.width || this.defaultHeaderWidth
314 previousHeader = undefined;