Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / docs / source / PivotAxis.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 <div id="cls-Ext.grid.PivotAxis"></div>/**
16  * @class Ext.grid.PivotAxis
17  * @extends Ext.Component
18  * <p>PivotAxis is a class that supports a {@link Ext.grid.PivotGrid}. Each PivotGrid contains two PivotAxis instances - the left
19  * axis and the top axis. Each PivotAxis defines an ordered set of dimensions, each of which should correspond to a field in a
20  * Store's Record (see {@link Ext.grid.PivotGrid} documentation for further explanation).</p>
21  * <p>Developers should have little interaction with the PivotAxis instances directly as most of their management is performed by
22  * the PivotGrid. An exception is the dynamic reconfiguration of axes at run time - to achieve this we use PivotAxis's 
23  * {@link #setDimensions} function and refresh the grid:</p>
24 <pre><code>
25 var pivotGrid = new Ext.grid.PivotGrid({
26     //some PivotGrid config here
27 });
28
29 //change the left axis dimensions
30 pivotGrid.leftAxis.setDimensions([
31     {
32         dataIndex: 'person',
33         direction: 'DESC',
34         width    : 100
35     },
36     {
37         dataIndex: 'product',
38         direction: 'ASC',
39         width    : 80
40     }
41 ]);
42
43 pivotGrid.view.refresh(true);
44 </code></pre>
45  * This clears the previous dimensions on the axis and redraws the grid with the new dimensions.
46  */
47 Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
48     <div id="cfg-Ext.grid.PivotAxis-orientation"></div>/**
49      * @cfg {String} orientation One of 'vertical' or 'horizontal'. Defaults to horizontal
50      */
51     orientation: 'horizontal',
52     
53     <div id="cfg-Ext.grid.PivotAxis-defaultHeaderWidth"></div>/**
54      * @cfg {Number} defaultHeaderWidth The width to render each row header that does not have a width specified via 
55      {@link #getRowGroupHeaders}. Defaults to 80.
56      */
57     defaultHeaderWidth: 80,
58     
59     /**
60      * @private
61      * @cfg {Number} paddingWidth The amount of padding used by each cell.
62      * 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
63      * the content box and border box measurement models
64      */
65     paddingWidth: 7,
66     
67     <div id="method-Ext.grid.PivotAxis-setDimensions"></div>/**
68      * Updates the dimensions used by this axis
69      * @param {Array} dimensions The new dimensions
70      */
71     setDimensions: function(dimensions) {
72         this.dimensions = dimensions;
73     },
74     
75     /**
76      * @private
77      * Builds the html table that contains the dimensions for this axis. This branches internally between vertical
78      * and horizontal orientations because the table structure is slightly different in each case
79      */
80     onRender: function(ct, position) {
81         var rows = this.orientation == 'horizontal'
82                  ? this.renderHorizontalRows()
83                  : this.renderVerticalRows();
84         
85         this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
86     },
87     
88     /**
89      * @private
90      * Specialised renderer for horizontal oriented axes
91      * @return {Object} The HTML Domspec for a horizontal oriented axis
92      */
93     renderHorizontalRows: function() {
94         var headers  = this.buildHeaders(),
95             rowCount = headers.length,
96             rows     = [],
97             cells, cols, colCount, i, j;
98         
99         for (i = 0; i < rowCount; i++) {
100             cells = [];
101             cols  = headers[i].items;
102             colCount = cols.length;
103
104             for (j = 0; j < colCount; j++) {
105                 cells.push({
106                     tag: 'td',
107                     html: cols[j].header,
108                     colspan: cols[j].span
109                 });
110             }
111
112             rows[i] = {
113                 tag: 'tr',
114                 cn: cells
115             };
116         }
117         
118         return rows;
119     },
120     
121     /**
122      * @private
123      * Specialised renderer for vertical oriented axes
124      * @return {Object} The HTML Domspec for a vertical oriented axis
125      */
126     renderVerticalRows: function() {
127         var headers  = this.buildHeaders(),
128             colCount = headers.length,
129             rowCells = [],
130             rows     = [],
131             rowCount, col, row, colWidth, i, j;
132         
133         for (i = 0; i < colCount; i++) {
134             col = headers[i];
135             colWidth = col.width || 80;
136             rowCount = col.items.length;
137             
138             for (j = 0; j < rowCount; j++) {
139                 row = col.items[j];
140                 
141                 rowCells[row.start] = rowCells[row.start] || [];
142                 rowCells[row.start].push({
143                     tag    : 'td',
144                     html   : row.header,
145                     rowspan: row.span,
146                     width  : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
147                 });
148             }
149         }
150         
151         rowCount = rowCells.length;
152         for (i = 0; i < rowCount; i++) {
153             rows[i] = {
154                 tag: 'tr',
155                 cn : rowCells[i]
156             };
157         }
158         
159         return rows;
160     },
161     
162     /**
163      * @private
164      * Returns the set of all unique tuples based on the bound store and dimension definitions.
165      * Internally we construct a new, temporary store to make use of the multi-sort capabilities of Store. In
166      * 4.x this functionality should have been moved to MixedCollection so this step should not be needed.
167      * @return {Array} All unique tuples
168      */
169     getTuples: function() {
170         var newStore = new Ext.data.Store({});
171         
172         newStore.data = this.store.data.clone();
173         newStore.fields = this.store.fields;
174         
175         var sorters    = [],
176             dimensions = this.dimensions,
177             length     = dimensions.length,
178             i;
179         
180         for (i = 0; i < length; i++) {
181             sorters.push({
182                 field    : dimensions[i].dataIndex,
183                 direction: dimensions[i].direction || 'ASC'
184             });
185         }
186         
187         newStore.sort(sorters);
188         
189         var records = newStore.data.items,
190             hashes  = [],
191             tuples  = [],
192             recData, hash, info, data, key;
193         
194         length = records.length;
195         
196         for (i = 0; i < length; i++) {
197             info = this.getRecordInfo(records[i]);
198             data = info.data;
199             hash = "";
200             
201             for (key in data) {
202                 hash += data[key] + '---';
203             }
204             
205             if (hashes.indexOf(hash) == -1) {
206                 hashes.push(hash);
207                 tuples.push(info);
208             }
209         }
210         
211         newStore.destroy();
212         
213         return tuples;
214     },
215     
216     /**
217      * @private
218      */
219     getRecordInfo: function(record) {
220         var dimensions = this.dimensions,
221             length  = dimensions.length,
222             data    = {},
223             dimension, dataIndex, i;
224         
225         //get an object containing just the data we are interested in based on the configured dimensions
226         for (i = 0; i < length; i++) {
227             dimension = dimensions[i];
228             dataIndex = dimension.dataIndex;
229             
230             data[dataIndex] = record.get(dataIndex);
231         }
232         
233         //creates a specialised matcher function for a given tuple. The returned function will return
234         //true if the record passed to it matches the dataIndex values of each dimension in this axis
235         var createMatcherFunction = function(data) {
236             return function(record) {
237                 for (var dataIndex in data) {
238                     if (record.get(dataIndex) != data[dataIndex]) {
239                         return false;
240                     }
241                 }
242                 
243                 return true;
244             };
245         };
246         
247         return {
248             data: data,
249             matcher: createMatcherFunction(data)
250         };
251     },
252     
253     /**
254      * @private
255      * Uses the calculated set of tuples to build an array of headers that can be rendered into a table using rowspan or
256      * colspan. Basically this takes the set of tuples and spans any cells that run into one another, so if we had dimensions
257      * of Person and Product and several tuples containing different Products for the same Person, those Products would be
258      * spanned.
259      * @return {Array} The headers
260      */
261     buildHeaders: function() {
262         var tuples     = this.getTuples(),
263             rowCount   = tuples.length,
264             dimensions = this.dimensions,
265             colCount   = dimensions.length,
266             headers    = [],
267             tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
268         
269         for (i = 0; i < colCount; i++) {
270             dimension = dimensions[i];
271             rows  = [];
272             span  = 0;
273             start = 0;
274             
275             for (j = 0; j < rowCount; j++) {
276                 tuple  = tuples[j];
277                 isLast = j == (rowCount - 1);
278                 currentHeader = tuple.data[dimension.dataIndex];
279                 
280                 /*
281                  * 'changed' indicates that we need to create a new cell. This should be true whenever the cell
282                  * above (previousHeader) is different from this cell, or when the cell on the previous dimension
283                  * changed (e.g. if the current dimension is Product and the previous was Person, we need to start
284                  * a new cell if Product is the same but Person changed, so we check the previous dimension and tuple)
285                  */
286                 changed = previousHeader != undefined && previousHeader != currentHeader;
287                 if (i > 0 && j > 0) {
288                     changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
289                 }
290                 
291                 if (changed) {                    
292                     rows.push({
293                         header: previousHeader,
294                         span  : span,
295                         start : start
296                     });
297                     
298                     start += span;
299                     span = 0;
300                 }
301                 
302                 if (isLast) {
303                     rows.push({
304                         header: currentHeader,
305                         span  : span + 1,
306                         start : start
307                     });
308                     
309                     start += span;
310                     span = 0;
311                 }
312                 
313                 previousHeader = currentHeader;
314                 span++;
315             }
316             
317             headers.push({
318                 items: rows,
319                 width: dimension.width || this.defaultHeaderWidth
320             });
321             
322             previousHeader = undefined;
323         }
324         
325         return headers;
326     }
327 });
328 </pre>    
329 </body>
330 </html>