Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / examples / ux / RowExpander.js
index 38b55ee..77d2a6f 100644 (file)
-/*!
- * Ext JS Library 3.3.1
- * Copyright(c) 2006-2010 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
-Ext.ns('Ext.ux.grid');
+// feature idea to enable Ajax loading and then the content
+// cache would actually make sense. Should we dictate that they use
+// data or support raw html as well?
 
 /**
- * @class Ext.ux.grid.RowExpander
- * @extends Ext.util.Observable
+ * @class Ext.ux.RowExpander
+ * @extends Ext.AbstractPlugin
  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
  *
  * @ptype rowexpander
  */
-Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {
+Ext.define('Ext.ux.RowExpander', {
+    extend: 'Ext.AbstractPlugin',
+    alias: 'plugin.rowexpander',
+
+    rowBodyTpl: null,
+
     /**
      * @cfg {Boolean} expandOnEnter
      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
      * key is pressed (defaults to <tt>true</tt>).
      */
-    expandOnEnter : true,
+    expandOnEnter: true,
+
     /**
      * @cfg {Boolean} expandOnDblClick
      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
      * (defaults to <tt>true</tt>).
      */
-    expandOnDblClick : true,
-
-    header : '',
-    width : 20,
-    sortable : false,
-    fixed : true,
-    hideable: false,
-    menuDisabled : true,
-    dataIndex : '',
-    id : 'expander',
-    lazyRender : true,
-    enableCaching : true,
-
-    constructor: function(config){
-        Ext.apply(this, config);
-
-        this.addEvents({
-            /**
-             * @event beforeexpand
-             * Fires before the row expands. Have the listener return false to prevent the row from expanding.
-             * @param {Object} this RowExpander object.
-             * @param {Object} Ext.data.Record Record for the selected row.
-             * @param {Object} body body element for the secondary row.
-             * @param {Number} rowIndex The current row index.
-             */
-            beforeexpand: true,
-            /**
-             * @event expand
-             * Fires after the row expands.
-             * @param {Object} this RowExpander object.
-             * @param {Object} Ext.data.Record Record for the selected row.
-             * @param {Object} body body element for the secondary row.
-             * @param {Number} rowIndex The current row index.
-             */
-            expand: true,
-            /**
-             * @event beforecollapse
-             * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
-             * @param {Object} this RowExpander object.
-             * @param {Object} Ext.data.Record Record for the selected row.
-             * @param {Object} body body element for the secondary row.
-             * @param {Number} rowIndex The current row index.
-             */
-            beforecollapse: true,
-            /**
-             * @event collapse
-             * Fires after the row collapses.
-             * @param {Object} this RowExpander object.
-             * @param {Object} Ext.data.Record Record for the selected row.
-             * @param {Object} body body element for the secondary row.
-             * @param {Number} rowIndex The current row index.
-             */
-            collapse: true
-        });
-
-        Ext.ux.grid.RowExpander.superclass.constructor.call(this);
-
-        if(this.tpl){
-            if(typeof this.tpl == 'string'){
-                this.tpl = new Ext.Template(this.tpl);
-            }
-            this.tpl.compile();
-        }
+    expandOnDblClick: true,
 
-        this.state = {};
-        this.bodyContent = {};
-    },
+    /**
+     * @cfg {Boolean} selectRowOnExpand
+     * <tt>true</tt> to select a row when clicking on the expander icon
+     * (defaults to <tt>false</tt>).
+     */
+    selectRowOnExpand: false,
 
-    getRowClass : function(record, rowIndex, p, ds){
-        p.cols = p.cols-1;
-        var content = this.bodyContent[record.id];
-        if(!content && !this.lazyRender){
-            content = this.getBodyContent(record, rowIndex);
-        }
-        if(content){
-            p.body = content;
-        }
-        return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
-    },
+    rowBodyTrSelector: '.x-grid-rowbody-tr',
+    rowBodyHiddenCls: 'x-grid-row-body-hidden',
+    rowCollapsedCls: 'x-grid-row-collapsed',
 
-    init : function(grid){
-        this.grid = grid;
 
-        var view = grid.getView();
-        view.getRowClass = this.getRowClass.createDelegate(this);
 
-        view.enableRowBody = true;
+    renderer: function(value, metadata, record, rowIdx, colIdx) {
+        if (colIdx === 0) {
+            metadata.tdCls = 'x-grid-td-expander';
+        }
+        return '<div class="x-grid-row-expander">&#160;</div>';
+    },
 
+    /**
+     * @event expandbody
+     * <b<Fired through the grid's View</b>
+     * @param {HtmlElement} rowNode The &lt;tr> element which owns the expanded row.
+     * @param {Ext.data.Model} record The record providing the data.
+     * @param {HtmlElement} expandRow The &lt;tr> element containing the expanded data.
+     */
+    /**
+     * @event collapsebody
+     * <b<Fired through the grid's View.</b>
+     * @param {HtmlElement} rowNode The &lt;tr> element which owns the expanded row.
+     * @param {Ext.data.Model} record The record providing the data.
+     * @param {HtmlElement} expandRow The &lt;tr> element containing the expanded data.
+     */
 
-        grid.on('render', this.onRender, this);
-        grid.on('destroy', this.onDestroy, this);
+    constructor: function() {
+        this.callParent(arguments);
+        var grid = this.getCmp();
+        this.recordsExpanded = {};
+        // <debug>
+        if (!this.rowBodyTpl) {
+            Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined.");
+        }
+        // </debug>
+        // TODO: if XTemplate/Template receives a template as an arg, should
+        // just return it back!
+        var rowBodyTpl = Ext.create('Ext.XTemplate', this.rowBodyTpl),
+            features = [{
+                ftype: 'rowbody',
+                columnId: this.getHeaderId(),
+                recordsExpanded: this.recordsExpanded,
+                rowBodyHiddenCls: this.rowBodyHiddenCls,
+                rowCollapsedCls: this.rowCollapsedCls,
+                getAdditionalData: this.getRowBodyFeatureData,
+                getRowBodyContents: function(data) {
+                    return rowBodyTpl.applyTemplate(data);
+                }
+            },{
+                ftype: 'rowwrap'
+            }];
+
+        if (grid.features) {
+            grid.features = features.concat(grid.features);
+        } else {
+            grid.features = features;
+        }
+
+        grid.columns.unshift(this.getHeaderConfig());
+        grid.on('afterlayout', this.onGridAfterLayout, this, {single: true});
     },
 
-    // @private
-    onRender: function() {
-        var grid = this.grid;
-        var mainBody = grid.getView().mainBody;
-        mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
-        if (this.expandOnEnter) {
-            this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
-                'enter' : this.onEnter,
-                scope: this
-            });
-        }
-        if (this.expandOnDblClick) {
-            grid.on('rowdblclick', this.onRowDblClick, this);
+    getHeaderId: function() {
+        if (!this.headerId) {
+            this.headerId = Ext.id();
         }
+        return this.headerId;
     },
-    
-    // @private    
-    onDestroy: function() {
-        if(this.keyNav){
-            this.keyNav.disable();
-            delete this.keyNav;
-        }
-        /*
-         * A majority of the time, the plugin will be destroyed along with the grid,
-         * which means the mainBody won't be available. On the off chance that the plugin
-         * isn't destroyed with the grid, take care of removing the listener.
-         */
-        var mainBody = this.grid.getView().mainBody;
-        if(mainBody){
-            mainBody.un('mousedown', this.onMouseDown, this);
-        }
+
+    getRowBodyFeatureData: function(data, idx, record, orig) {
+        var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments),
+            id = this.columnId;
+        o.rowBodyColspan = o.rowBodyColspan - 1;
+        o.rowBody = this.getRowBodyContents(data);
+        o.rowCls = this.recordsExpanded[record.internalId] ? '' : this.rowCollapsedCls;
+        o.rowBodyCls = this.recordsExpanded[record.internalId] ? '' : this.rowBodyHiddenCls;
+        o[id + '-tdAttr'] = ' valign="top" rowspan="2" ';
+        if (orig[id+'-tdAttr']) {
+            o[id+'-tdAttr'] += orig[id+'-tdAttr'];
+        }
+        return o;
     },
-    // @private
-    onRowDblClick: function(grid, rowIdx, e) {
-        this.toggleRow(rowIdx);
+
+    onGridAfterLayout: function() {
+        var grid = this.getCmp(),
+            view, viewEl;
+
+        if (!grid.hasView) {
+            this.getCmp().on('afterlayout', this.onGridAfterLayout, this, {single: true});
+        } else {
+            view = grid.down('gridview');
+            viewEl = view.getEl();
+
+            if (this.expandOnEnter) {
+                this.keyNav = Ext.create('Ext.KeyNav', viewEl, {
+                    'enter' : this.onEnter,
+                    scope: this
+                });
+            }
+            if (this.expandOnDblClick) {
+                view.on('itemdblclick', this.onDblClick, this);
+            }
+            this.view = view;
+        }
     },
 
     onEnter: function(e) {
-        var g = this.grid;
-        var sm = g.getSelectionModel();
-        var sels = sm.getSelections();
-        for (var i = 0, len = sels.length; i < len; i++) {
-            var rowIdx = g.getStore().indexOf(sels[i]);
+        var view = this.view,
+            ds   = view.store,
+            sm   = view.getSelectionModel(),
+            sels = sm.getSelection(),
+            ln   = sels.length,
+            i = 0,
+            rowIdx;
+
+        for (; i < ln; i++) {
+            rowIdx = ds.indexOf(sels[i]);
             this.toggleRow(rowIdx);
         }
     },
 
-    getBodyContent : function(record, index){
-        if(!this.enableCaching){
-            return this.tpl.apply(record.data);
-        }
-        var content = this.bodyContent[record.id];
-        if(!content){
-            content = this.tpl.apply(record.data);
-            this.bodyContent[record.id] = content;
-        }
-        return content;
+    toggleRow: function(rowIdx) {
+        var rowNode = this.view.getNode(rowIdx),
+            row = Ext.get(rowNode),
+            nextBd = Ext.get(row).down(this.rowBodyTrSelector),
+            record = this.view.getRecord(rowNode);
+
+        if (row.hasCls(this.rowCollapsedCls)) {
+            row.removeCls(this.rowCollapsedCls);
+            nextBd.removeCls(this.rowBodyHiddenCls);
+            this.recordsExpanded[record.internalId] = true;
+            this.view.fireEvent('expandbody', rowNode, record, nextBd.dom);
+        } else {
+            row.addCls(this.rowCollapsedCls);
+            nextBd.addCls(this.rowBodyHiddenCls);
+            this.recordsExpanded[record.internalId] = false;
+            this.view.fireEvent('collapsebody', rowNode, record, nextBd.dom);
+        }
+        this.view.up('gridpanel').invalidateScroller();
     },
 
-    onMouseDown : function(e, t){
-        e.stopEvent();
-        var row = e.getTarget('.x-grid3-row');
-        this.toggleRow(row);
-    },
+    onDblClick: function(view, cell, rowIdx, cellIndex, e) {
 
-    renderer : function(v, p, record){
-        p.cellAttr = 'rowspan="2"';
-        return '<div class="x-grid3-row-expander">&#160;</div>';
+        this.toggleRow(rowIdx);
     },
 
-    beforeExpand : function(record, body, rowIndex){
-        if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
-            if(this.tpl && this.lazyRender){
-                body.innerHTML = this.getBodyContent(record, rowIndex);
+    getHeaderConfig: function() {
+        var me                = this,
+            toggleRow         = Ext.Function.bind(me.toggleRow, me),
+            selectRowOnExpand = me.selectRowOnExpand;
+
+        return {
+            id: this.getHeaderId(),
+            width: 24,
+            sortable: false,
+            fixed: true,
+            draggable: false,
+            hideable: false,
+            menuDisabled: true,
+            cls: Ext.baseCSSPrefix + 'grid-header-special',
+            renderer: function(value, metadata) {
+                metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
+
+                return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander">&#160;</div>';
+            },
+            processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
+                if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) {
+                    var row = e.getTarget('.x-grid-row');
+                    toggleRow(row);
+                    return selectRowOnExpand;
+                }
             }
-            return true;
-        }else{
-            return false;
-        }
-    },
-
-    toggleRow : function(row){
-        if(typeof row == 'number'){
-            row = this.grid.view.getRow(row);
-        }
-        this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
-    },
-
-    expandRow : function(row){
-        if(typeof row == 'number'){
-            row = this.grid.view.getRow(row);
-        }
-        var record = this.grid.store.getAt(row.rowIndex);
-        var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
-        if(this.beforeExpand(record, body, row.rowIndex)){
-            this.state[record.id] = true;
-            Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
-            this.fireEvent('expand', this, record, body, row.rowIndex);
-        }
-    },
-
-    collapseRow : function(row){
-        if(typeof row == 'number'){
-            row = this.grid.view.getRow(row);
-        }
-        var record = this.grid.store.getAt(row.rowIndex);
-        var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
-        if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
-            this.state[record.id] = false;
-            Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
-            this.fireEvent('collapse', this, record, body, row.rowIndex);
-        }
+        };
     }
 });
-
-Ext.preg('rowexpander', Ext.ux.grid.RowExpander);
-
-//backwards compat
-Ext.grid.RowExpander = Ext.ux.grid.RowExpander;
\ No newline at end of file