Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / View2.html
index b13343e..af812b4 100644 (file)
@@ -3,8 +3,8 @@
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>The source code</title>
-  <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
-  <script type="text/javascript" src="../prettify/prettify.js"></script>
+  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
   <style type="text/css">
     .highlight { display: block; background-color: #ddd; }
   </style>
   </script>
 </head>
 <body onload="prettyPrint(); highlight();">
-  <pre class="prettyprint lang-js"><span id='Ext-grid-View'>/**
-</span> * @class Ext.grid.View
- * @extends Ext.view.Table
-
-The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
-{@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
-option is passed to the grid:
-
-    Ext.create('Ext.grid.Panel', {
-        // other options
-        viewConfig: {
-            stripeRows: false
-        }
-    });
-    
-__Drag Drop__
-Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
-when creating the view.
-
-    Ext.create('Ext.grid.Panel', {
-        // other options
-        viewConfig: {
-            plugins: {
-                ddGroup: 'people-group',
-                ptype: 'gridviewdragdrop',
-                enableDrop: false
-            }
-        }
-    });
-
- * @markdown
+  <pre class="prettyprint lang-js"><span id='Ext-tree-View'>/**
+</span> * Used as a view by {@link Ext.tree.Panel TreePanel}.
  */
-Ext.define('Ext.grid.View', {
+Ext.define('Ext.tree.View', {
     extend: 'Ext.view.Table',
-    alias: 'widget.gridview',
-
-<span id='Ext-grid-View-cfg-stripeRows'>    /**
-</span>     * @cfg {Boolean} stripeRows &lt;tt&gt;true&lt;/tt&gt; to stripe the rows. Default is &lt;tt&gt;false&lt;/tt&gt;.
-     * &lt;p&gt;This causes the CSS class &lt;tt&gt;&lt;b&gt;x-grid-row-alt&lt;/b&gt;&lt;/tt&gt; to be added to alternate rows of
-     * the grid. A default CSS rule is provided which sets a background color, but you can override this
-     * with a rule which either overrides the &lt;b&gt;background-color&lt;/b&gt; style using the '!important'
-     * modifier, or which uses a CSS selector of higher specificity.&lt;/p&gt;
+    alias: 'widget.treeview',
+
+    loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
+    expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
+
+    expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
+    checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
+    expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
+
+    // Class to add to the node wrap element used to hold nodes when a parent is being
+    // collapsed or expanded. During the animation, UI interaction is forbidden by testing
+    // for an ancestor node with this class.
+    nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
+
+    blockRefresh: true,
+
+<span id='Ext-tree-View-cfg-rootVisible'>    /** 
+</span>     * @cfg {Boolean} rootVisible
+     * False to hide the root node.
      */
-    stripeRows: true,
+    rootVisible: true,
+
+<span id='Ext-tree-View-cfg-animate'>    /** 
+</span>     * @cfg {Boolean} animate
+     * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
+     */
+
+    expandDuration: 250,
+    collapseDuration: 250,
     
-    invalidateScrollerOnRefresh: true,
+    toggleOnDblClick: true,
+
+    initComponent: function() {
+        var me = this;
+        
+        if (me.initialConfig.animate === undefined) {
+            me.animate = Ext.enableFx;
+        }
+        
+        me.store = Ext.create('Ext.data.NodeStore', {
+            recursive: true,
+            rootVisible: me.rootVisible,
+            listeners: {
+                beforeexpand: me.onBeforeExpand,
+                expand: me.onExpand,
+                beforecollapse: me.onBeforeCollapse,
+                collapse: me.onCollapse,
+                scope: me
+            }
+        });
+        
+        if (me.node) {
+            me.setRootNode(me.node);
+        }
+        me.animQueue = {};
+        me.callParent(arguments);
+    },
+
+    processUIEvent: function(e) {
+        // If the clicked node is part of an animation, ignore the click.
+        // This is because during a collapse animation, the associated Records
+        // will already have been removed from the Store, and the event is not processable.
+        if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
+            return false;
+        }
+        return this.callParent(arguments);
+    },
+
+    onClear: function(){
+        this.store.removeAll();    
+    },
+
+    setRootNode: function(node) {
+        var me = this;        
+        me.store.setNode(node);
+        me.node = node;
+        if (!me.rootVisible) {
+            node.expand();
+        }
+    },
     
-<span id='Ext-grid-View-method-scrollToTop'>    /**
-</span>     * Scroll the GridView to the top by scrolling the scroller.
-     * @private
-     */
-    scrollToTop : function(){
-        if (this.rendered) {
-            var section = this.ownerCt,
-                verticalScroller = section.verticalScroller;
-                
-            if (verticalScroller) {
-                verticalScroller.scrollToTop();
+    onRender: function() {
+        var me = this,
+            el;
+
+        me.callParent(arguments);
+
+        el = me.el;
+        el.on({
+            scope: me,
+            delegate: me.expanderSelector,
+            mouseover: me.onExpanderMouseOver,
+            mouseout: me.onExpanderMouseOut
+        });
+        el.on({
+            scope: me,
+            delegate: me.checkboxSelector,
+            click: me.onCheckboxChange
+        });
+    },
+
+    onCheckboxChange: function(e, t) {
+        var me = this,
+            item = e.getTarget(me.getItemSelector(), me.getTargetEl());
+            
+        if (item) {
+            me.onCheckChange(me.getRecord(item));
+        }
+    },
+    
+    onCheckChange: function(record){
+        var checked = record.get('checked');
+        if (Ext.isBoolean(checked)) {
+            checked = !checked;
+            record.set('checked', checked);
+            this.fireEvent('checkchange', record, checked);
+        }
+    },
+
+    getChecked: function() {
+        var checked = [];
+        this.node.cascadeBy(function(rec){
+            if (rec.get('checked')) {
+                checked.push(rec);
             }
+        });
+        return checked;
+    },
+    
+    isItemChecked: function(rec){
+        return rec.get('checked');
+    },
+
+    createAnimWrap: function(record, index) {
+        var thHtml = '',
+            headerCt = this.panel.headerCt,
+            headers = headerCt.getGridColumns(),
+            i = 0, len = headers.length, item,
+            node = this.getNode(record),
+            tmpEl, nodeEl;
+
+        for (; i &lt; len; i++) {
+            item = headers[i];
+            thHtml += '&lt;th style=&quot;width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;&quot;&gt;&lt;/th&gt;';
         }
+
+        nodeEl = Ext.get(node);        
+        tmpEl = nodeEl.insertSibling({
+            tag: 'tr',
+            html: [
+                '&lt;td colspan=&quot;' + headerCt.getColumnCount() + '&quot;&gt;',
+                    '&lt;div class=&quot;' + this.nodeAnimWrapCls + '&quot;&gt;',
+                        '&lt;table class=&quot;' + Ext.baseCSSPrefix + 'grid-table&quot; style=&quot;width: ' + headerCt.getFullWidth() + 'px;&quot;&gt;&lt;tbody&gt;',
+                            thHtml,
+                        '&lt;/tbody&gt;&lt;/table&gt;',
+                    '&lt;/div&gt;',
+                '&lt;/td&gt;'
+            ].join('')
+        }, 'after');
+
+        return {
+            record: record,
+            node: node,
+            el: tmpEl,
+            expanding: false,
+            collapsing: false,
+            animating: false,
+            animateEl: tmpEl.down('div'),
+            targetEl: tmpEl.down('tbody')
+        };
     },
 
-    // after adding a row stripe rows from then on
-    onAdd: function(ds, records, index) {
-        this.callParent(arguments);
-        this.doStripeRows(index);
+    getAnimWrap: function(parent) {
+        if (!this.animate) {
+            return null;
+        }
+
+        // We are checking to see which parent is having the animation wrap
+        while (parent) {
+            if (parent.animWrap) {
+                return parent.animWrap;
+            }
+            parent = parent.parentNode;
+        }
+        return null;
+    },
+
+    doAdd: function(nodes, records, index) {
+        // If we are adding records which have a parent that is currently expanding
+        // lets add them to the animation wrap
+        var me = this,
+            record = records[0],
+            parent = record.parentNode,
+            a = me.all.elements,
+            relativeIndex = 0,
+            animWrap = me.getAnimWrap(parent),
+            targetEl, children, len;
+
+        if (!animWrap || !animWrap.expanding) {
+            me.resetScrollers();
+            return me.callParent(arguments);
+        }
+
+        // We need the parent that has the animWrap, not the nodes parent
+        parent = animWrap.record;
+        
+        // If there is an anim wrap we do our special magic logic
+        targetEl = animWrap.targetEl;
+        children = targetEl.dom.childNodes;
+        
+        // We subtract 1 from the childrens length because we have a tr in there with the th'es
+        len = children.length - 1;
+        
+        // The relative index is the index in the full flat collection minus the index of the wraps parent
+        relativeIndex = index - me.indexOf(parent) - 1;
+        
+        // If we are adding records to the wrap that have a higher relative index then there are currently children
+        // it means we have to append the nodes to the wrap
+        if (!len || relativeIndex &gt;= len) {
+            targetEl.appendChild(nodes);
+        }
+        // If there are already more children then the relative index it means we are adding child nodes of
+        // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
+        else {
+            // +1 because of the tr with th'es that is already there
+            Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
+        }
+
+        // We also have to update the CompositeElementLite collection of the DataView
+        Ext.Array.insert(a, index, nodes);
+        
+        // If we were in an animation we need to now change the animation
+        // because the targetEl just got higher.
+        if (animWrap.isAnimating) {
+            me.onExpand(parent);
+        }
     },
     
-    // after removing a row stripe rows from then on
-    onRemove: function(ds, records, index) {
-        this.callParent(arguments);
-        this.doStripeRows(index);
+    beginBulkUpdate: function(){
+        this.bulkUpdate = true;
+        this.ownerCt.changingScrollbars = true;  
     },
     
-    onUpdate: function(ds, record, operation) {
-        var index = ds.indexOf(record);
-        this.callParent(arguments);
-        this.doStripeRows(index, index);
+    endBulkUpdate: function(){
+        var me = this,
+            ownerCt = me.ownerCt;
+        
+        me.bulkUpdate = false;
+        me.ownerCt.changingScrollbars = true;  
+        me.resetScrollers();  
+    },
+    
+    onRemove : function(ds, record, index) {
+        var me = this,
+            bulk = me.bulkUpdate;
+
+        me.doRemove(record, index);
+        if (!bulk) {
+            me.updateIndexes(index);
+        }
+        if (me.store.getCount() === 0){
+            me.refresh();
+        }
+        if (!bulk) {
+            me.fireEvent('itemremove', record, index);
+        }
     },
     
-<span id='Ext-grid-View-method-doStripeRows'>    /**
-</span>     * Stripe rows from a particular row index
-     * @param {Number} startRow
-     * @param {Number} endRow Optional argument specifying the last row to process. By default process up to the last row.
+    doRemove: function(record, index) {
+        // If we are adding records which have a parent that is currently expanding
+        // lets add them to the animation wrap
+        var me = this,
+            parent = record.parentNode,
+            all = me.all,
+            animWrap = me.getAnimWrap(record),
+            node = all.item(index).dom;
+
+        if (!animWrap || !animWrap.collapsing) {
+            me.resetScrollers();
+            return me.callParent(arguments);
+        }
+
+        animWrap.targetEl.appendChild(node);
+        all.removeElement(index);
+    },
+
+    onBeforeExpand: function(parent, records, index) {
+        var me = this,
+            animWrap;
+            
+        if (!me.rendered || !me.animate) {
+            return;
+        }
+
+        if (me.getNode(parent)) {
+            animWrap = me.getAnimWrap(parent);
+            if (!animWrap) {
+                animWrap = parent.animWrap = me.createAnimWrap(parent);
+                animWrap.animateEl.setHeight(0);
+            }
+            else if (animWrap.collapsing) {
+                // If we expand this node while it is still expanding then we
+                // have to remove the nodes from the animWrap.
+                animWrap.targetEl.select(me.itemSelector).remove();
+            } 
+            animWrap.expanding = true;
+            animWrap.collapsing = false;
+        }
+    },
+
+    onExpand: function(parent) {
+        var me = this,
+            queue = me.animQueue,
+            id = parent.getId(),
+            animWrap,
+            animateEl, 
+            targetEl,
+            queueItem;        
+        
+        if (me.singleExpand) {
+            me.ensureSingleExpand(parent);
+        }
+        
+        animWrap = me.getAnimWrap(parent);
+
+        if (!animWrap) {
+            me.resetScrollers();
+            return;
+        }
+        
+        animateEl = animWrap.animateEl;
+        targetEl = animWrap.targetEl;
+
+        animateEl.stopAnimation();
+        // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
+        queue[id] = true;
+        animateEl.slideIn('t', {
+            duration: me.expandDuration,
+            listeners: {
+                scope: me,
+                lastframe: function() {
+                    // Move all the nodes out of the anim wrap to their proper location
+                    animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
+                    animWrap.el.remove();
+                    me.resetScrollers();
+                    delete animWrap.record.animWrap;
+                    delete queue[id];
+                }
+            }
+        });
+        
+        animWrap.isAnimating = true;
+    },
+    
+    resetScrollers: function(){
+        if (!this.bulkUpdate) {
+            var panel = this.panel;
+            
+            panel.determineScrollbars();
+            panel.invalidateScroller();
+        }
+    },
+
+    onBeforeCollapse: function(parent, records, index) {
+        var me = this,
+            animWrap;
+            
+        if (!me.rendered || !me.animate) {
+            return;
+        }
+
+        if (me.getNode(parent)) {
+            animWrap = me.getAnimWrap(parent);
+            if (!animWrap) {
+                animWrap = parent.animWrap = me.createAnimWrap(parent, index);
+            }
+            else if (animWrap.expanding) {
+                // If we collapse this node while it is still expanding then we
+                // have to remove the nodes from the animWrap.
+                animWrap.targetEl.select(this.itemSelector).remove();
+            }
+            animWrap.expanding = false;
+            animWrap.collapsing = true;
+        }
+    },
+    
+    onCollapse: function(parent) {
+        var me = this,
+            queue = me.animQueue,
+            id = parent.getId(),
+            animWrap = me.getAnimWrap(parent),
+            animateEl, targetEl;
+
+        if (!animWrap) {
+            me.resetScrollers();
+            return;
+        }
+        
+        animateEl = animWrap.animateEl;
+        targetEl = animWrap.targetEl;
+
+        queue[id] = true;
+        
+        // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
+        animateEl.stopAnimation();
+        animateEl.slideOut('t', {
+            duration: me.collapseDuration,
+            listeners: {
+                scope: me,
+                lastframe: function() {
+                    animWrap.el.remove();
+                    delete animWrap.record.animWrap;
+                    me.resetScrollers();
+                    delete queue[id];
+                }             
+            }
+        });
+        animWrap.isAnimating = true;
+    },
+    
+<span id='Ext-tree-View-method-isAnimating'>    /**
+</span>     * Checks if a node is currently undergoing animation
      * @private
+     * @param {Ext.data.Model} node The node
+     * @return {Boolean} True if the node is animating
      */
-    doStripeRows: function(startRow, endRow) {
-        // ensure stripeRows configuration is turned on
-        if (this.stripeRows) {
-            var rows   = this.getNodes(startRow, endRow),
-                rowsLn = rows.length,
-                i      = 0,
-                row;
-                
-            for (; i &lt; rowsLn; i++) {
-                row = rows[i];
-                // Remove prior applied row classes.
-                row.className = row.className.replace(this.rowClsRe, ' ');
-                startRow++;
-                // Every odd row will get an additional cls
-                if (startRow % 2 === 0) {
-                    row.className += (' ' + this.altRowCls);
+    isAnimating: function(node) {
+        return !!this.animQueue[node.getId()];    
+    },
+    
+    collectData: function(records) {
+        var data = this.callParent(arguments),
+            rows = data.rows,
+            len = rows.length,
+            i = 0,
+            row, record;
+            
+        for (; i &lt; len; i++) {
+            row = rows[i];
+            record = records[i];
+            if (record.get('qtip')) {
+                row.rowAttr = 'data-qtip=&quot;' + record.get('qtip') + '&quot;';
+                if (record.get('qtitle')) {
+                    row.rowAttr += ' ' + 'data-qtitle=&quot;' + record.get('qtitle') + '&quot;';
                 }
             }
+            if (record.isExpanded()) {
+                row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
+            }
+            if (record.isLoading()) {
+                row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
+            }
         }
+        
+        return data;
+    },
+    
+<span id='Ext-tree-View-method-expand'>    /**
+</span>     * Expands a record that is loaded in the view.
+     * @param {Ext.data.Model} record The record to expand
+     * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
+     * @param {Function} callback (optional) The function to run after the expand is completed
+     * @param {Object} scope (optional) The scope of the callback function.
+     */
+    expand: function(record, deep, callback, scope) {
+        return record.expand(deep, callback, scope);
+    },
+    
+<span id='Ext-tree-View-method-collapse'>    /**
+</span>     * Collapses a record that is loaded in the view.
+     * @param {Ext.data.Model} record The record to collapse
+     * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
+     * @param {Function} callback (optional) The function to run after the collapse is completed
+     * @param {Object} scope (optional) The scope of the callback function.
+     */
+    collapse: function(record, deep, callback, scope) {
+        return record.collapse(deep, callback, scope);
+    },
+    
+<span id='Ext-tree-View-method-toggle'>    /**
+</span>     * Toggles a record between expanded and collapsed.
+     * @param {Ext.data.Model} recordInstance
+     */
+    toggle: function(record) {
+        this[record.isExpanded() ? 'collapse' : 'expand'](record);
     },
     
-    refresh: function(firstPass) {
+    onItemDblClick: function(record, item, index) {
         this.callParent(arguments);
-        this.doStripeRows(0);
-        // TODO: Remove gridpanel dependency
-        var g = this.up('gridpanel');
-        if (g &amp;&amp; this.invalidateScrollerOnRefresh) {
-            g.invalidateScroller();
+        if (this.toggleOnDblClick) {
+            this.toggle(record);
+        }
+    },
+    
+    onBeforeItemMouseDown: function(record, item, index, e) {
+        if (e.getTarget(this.expanderSelector, item)) {
+            return false;
+        }
+        return this.callParent(arguments);
+    },
+    
+    onItemClick: function(record, item, index, e) {
+        if (e.getTarget(this.expanderSelector, item)) {
+            this.toggle(record);
+            return false;
+        }
+        return this.callParent(arguments);
+    },
+    
+    onExpanderMouseOver: function(e, t) {
+        e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
+    },
+    
+    onExpanderMouseOut: function(e, t) {
+        e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
+    },
+    
+<span id='Ext-tree-View-method-getTreeStore'>    /**
+</span>     * Gets the base TreeStore from the bound TreePanel.
+     */
+    getTreeStore: function() {
+        return this.panel.store;
+    },    
+    
+    ensureSingleExpand: function(node) {
+        var parent = node.parentNode;
+        if (parent) {
+            parent.eachChild(function(child) {
+                if (child !== node &amp;&amp; child.isExpanded()) {
+                    child.collapse();
+                }
+            });
         }
     }
-});
-</pre>
+});</pre>
 </body>
 </html>