Upgrade to ExtJS 3.3.0 - Released 10/06/2010
[extjs.git] / docs / source / PivotAxis.html
diff --git a/docs/source/PivotAxis.html b/docs/source/PivotAxis.html
new file mode 100644 (file)
index 0000000..6c916ae
--- /dev/null
@@ -0,0 +1,330 @@
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
+  <title>The source code</title>
+    <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+    <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
+</head>
+<body  onload="prettyPrint();">
+    <pre class="prettyprint lang-js">/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+<div id="cls-Ext.grid.PivotAxis"></div>/**
+ * @class Ext.grid.PivotAxis
+ * @extends Ext.Component
+ * <p>PivotAxis is a class that supports a {@link Ext.grid.PivotGrid}. Each PivotGrid contains two PivotAxis instances - the left
+ * axis and the top axis. Each PivotAxis defines an ordered set of dimensions, each of which should correspond to a field in a
+ * Store's Record (see {@link Ext.grid.PivotGrid} documentation for further explanation).</p>
+ * <p>Developers should have little interaction with the PivotAxis instances directly as most of their management is performed by
+ * the PivotGrid. An exception is the dynamic reconfiguration of axes at run time - to achieve this we use PivotAxis's 
+ * {@link #setDimensions} function and refresh the grid:</p>
+<pre><code>
+var pivotGrid = new Ext.grid.PivotGrid({
+    //some PivotGrid config here
+});
+
+//change the left axis dimensions
+pivotGrid.leftAxis.setDimensions([
+    {
+        dataIndex: 'person',
+        direction: 'DESC',
+        width    : 100
+    },
+    {
+        dataIndex: 'product',
+        direction: 'ASC',
+        width    : 80
+    }
+]);
+
+pivotGrid.view.refresh(true);
+</code></pre>
+ * This clears the previous dimensions on the axis and redraws the grid with the new dimensions.
+ */
+Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
+    <div id="cfg-Ext.grid.PivotAxis-orientation"></div>/**
+     * @cfg {String} orientation One of 'vertical' or 'horizontal'. Defaults to horizontal
+     */
+    orientation: 'horizontal',
+    
+    <div id="cfg-Ext.grid.PivotAxis-defaultHeaderWidth"></div>/**
+     * @cfg {Number} defaultHeaderWidth The width to render each row header that does not have a width specified via 
+     {@link #getRowGroupHeaders}. Defaults to 80.
+     */
+    defaultHeaderWidth: 80,
+    
+    /**
+     * @private
+     * @cfg {Number} paddingWidth The amount of padding used by each cell.
+     * 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
+     * the content box and border box measurement models
+     */
+    paddingWidth: 7,
+    
+    <div id="method-Ext.grid.PivotAxis-setDimensions"></div>/**
+     * Updates the dimensions used by this axis
+     * @param {Array} dimensions The new dimensions
+     */
+    setDimensions: function(dimensions) {
+        this.dimensions = dimensions;
+    },
+    
+    /**
+     * @private
+     * Builds the html table that contains the dimensions for this axis. This branches internally between vertical
+     * and horizontal orientations because the table structure is slightly different in each case
+     */
+    onRender: function(ct, position) {
+        var rows = this.orientation == 'horizontal'
+                 ? this.renderHorizontalRows()
+                 : this.renderVerticalRows();
+        
+        this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
+    },
+    
+    /**
+     * @private
+     * Specialised renderer for horizontal oriented axes
+     * @return {Object} The HTML Domspec for a horizontal oriented axis
+     */
+    renderHorizontalRows: function() {
+        var headers  = this.buildHeaders(),
+            rowCount = headers.length,
+            rows     = [],
+            cells, cols, colCount, i, j;
+        
+        for (i = 0; i < rowCount; i++) {
+            cells = [];
+            cols  = headers[i].items;
+            colCount = cols.length;
+
+            for (j = 0; j < colCount; j++) {
+                cells.push({
+                    tag: 'td',
+                    html: cols[j].header,
+                    colspan: cols[j].span
+                });
+            }
+
+            rows[i] = {
+                tag: 'tr',
+                cn: cells
+            };
+        }
+        
+        return rows;
+    },
+    
+    /**
+     * @private
+     * Specialised renderer for vertical oriented axes
+     * @return {Object} The HTML Domspec for a vertical oriented axis
+     */
+    renderVerticalRows: function() {
+        var headers  = this.buildHeaders(),
+            colCount = headers.length,
+            rowCells = [],
+            rows     = [],
+            rowCount, col, row, colWidth, i, j;
+        
+        for (i = 0; i < colCount; i++) {
+            col = headers[i];
+            colWidth = col.width || 80;
+            rowCount = col.items.length;
+            
+            for (j = 0; j < rowCount; j++) {
+                row = col.items[j];
+                
+                rowCells[row.start] = rowCells[row.start] || [];
+                rowCells[row.start].push({
+                    tag    : 'td',
+                    html   : row.header,
+                    rowspan: row.span,
+                    width  : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
+                });
+            }
+        }
+        
+        rowCount = rowCells.length;
+        for (i = 0; i < rowCount; i++) {
+            rows[i] = {
+                tag: 'tr',
+                cn : rowCells[i]
+            };
+        }
+        
+        return rows;
+    },
+    
+    /**
+     * @private
+     * Returns the set of all unique tuples based on the bound store and dimension definitions.
+     * Internally we construct a new, temporary store to make use of the multi-sort capabilities of Store. In
+     * 4.x this functionality should have been moved to MixedCollection so this step should not be needed.
+     * @return {Array} All unique tuples
+     */
+    getTuples: function() {
+        var newStore = new Ext.data.Store({});
+        
+        newStore.data = this.store.data.clone();
+        newStore.fields = this.store.fields;
+        
+        var sorters    = [],
+            dimensions = this.dimensions,
+            length     = dimensions.length,
+            i;
+        
+        for (i = 0; i < length; i++) {
+            sorters.push({
+                field    : dimensions[i].dataIndex,
+                direction: dimensions[i].direction || 'ASC'
+            });
+        }
+        
+        newStore.sort(sorters);
+        
+        var records = newStore.data.items,
+            hashes  = [],
+            tuples  = [],
+            recData, hash, info, data, key;
+        
+        length = records.length;
+        
+        for (i = 0; i < length; i++) {
+            info = this.getRecordInfo(records[i]);
+            data = info.data;
+            hash = "";
+            
+            for (key in data) {
+                hash += data[key] + '---';
+            }
+            
+            if (hashes.indexOf(hash) == -1) {
+                hashes.push(hash);
+                tuples.push(info);
+            }
+        }
+        
+        newStore.destroy();
+        
+        return tuples;
+    },
+    
+    /**
+     * @private
+     */
+    getRecordInfo: function(record) {
+        var dimensions = this.dimensions,
+            length  = dimensions.length,
+            data    = {},
+            dimension, dataIndex, i;
+        
+        //get an object containing just the data we are interested in based on the configured dimensions
+        for (i = 0; i < length; i++) {
+            dimension = dimensions[i];
+            dataIndex = dimension.dataIndex;
+            
+            data[dataIndex] = record.get(dataIndex);
+        }
+        
+        //creates a specialised matcher function for a given tuple. The returned function will return
+        //true if the record passed to it matches the dataIndex values of each dimension in this axis
+        var createMatcherFunction = function(data) {
+            return function(record) {
+                for (var dataIndex in data) {
+                    if (record.get(dataIndex) != data[dataIndex]) {
+                        return false;
+                    }
+                }
+                
+                return true;
+            };
+        };
+        
+        return {
+            data: data,
+            matcher: createMatcherFunction(data)
+        };
+    },
+    
+    /**
+     * @private
+     * Uses the calculated set of tuples to build an array of headers that can be rendered into a table using rowspan or
+     * colspan. Basically this takes the set of tuples and spans any cells that run into one another, so if we had dimensions
+     * of Person and Product and several tuples containing different Products for the same Person, those Products would be
+     * spanned.
+     * @return {Array} The headers
+     */
+    buildHeaders: function() {
+        var tuples     = this.getTuples(),
+            rowCount   = tuples.length,
+            dimensions = this.dimensions,
+            colCount   = dimensions.length,
+            headers    = [],
+            tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
+        
+        for (i = 0; i < colCount; i++) {
+            dimension = dimensions[i];
+            rows  = [];
+            span  = 0;
+            start = 0;
+            
+            for (j = 0; j < rowCount; j++) {
+                tuple  = tuples[j];
+                isLast = j == (rowCount - 1);
+                currentHeader = tuple.data[dimension.dataIndex];
+                
+                /*
+                 * 'changed' indicates that we need to create a new cell. This should be true whenever the cell
+                 * above (previousHeader) is different from this cell, or when the cell on the previous dimension
+                 * changed (e.g. if the current dimension is Product and the previous was Person, we need to start
+                 * a new cell if Product is the same but Person changed, so we check the previous dimension and tuple)
+                 */
+                changed = previousHeader != undefined && previousHeader != currentHeader;
+                if (i > 0 && j > 0) {
+                    changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
+                }
+                
+                if (changed) {                    
+                    rows.push({
+                        header: previousHeader,
+                        span  : span,
+                        start : start
+                    });
+                    
+                    start += span;
+                    span = 0;
+                }
+                
+                if (isLast) {
+                    rows.push({
+                        header: currentHeader,
+                        span  : span + 1,
+                        start : start
+                    });
+                    
+                    start += span;
+                    span = 0;
+                }
+                
+                previousHeader = currentHeader;
+                span++;
+            }
+            
+            headers.push({
+                items: rows,
+                width: dimension.width || this.defaultHeaderWidth
+            });
+            
+            previousHeader = undefined;
+        }
+        
+        return headers;
+    }
+});
+</pre>    
+</body>
+</html>
\ No newline at end of file