Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / View2.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-tree-View'>/**
19 </span> * Used as a view by {@link Ext.tree.Panel TreePanel}.
20  */
21 Ext.define('Ext.tree.View', {
22     extend: 'Ext.view.Table',
23     alias: 'widget.treeview',
24
25     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
26     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
27
28     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
29     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
30     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
31
32     // Class to add to the node wrap element used to hold nodes when a parent is being
33     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
34     // for an ancestor node with this class.
35     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
36
37     blockRefresh: true,
38
39 <span id='Ext-tree-View-cfg-rootVisible'>    /** 
40 </span>     * @cfg {Boolean} rootVisible
41      * False to hide the root node.
42      */
43     rootVisible: true,
44
45 <span id='Ext-tree-View-cfg-animate'>    /** 
46 </span>     * @cfg {Boolean} animate
47      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
48      */
49
50     expandDuration: 250,
51     collapseDuration: 250,
52     
53     toggleOnDblClick: true,
54
55     initComponent: function() {
56         var me = this;
57         
58         if (me.initialConfig.animate === undefined) {
59             me.animate = Ext.enableFx;
60         }
61         
62         me.store = Ext.create('Ext.data.NodeStore', {
63             recursive: true,
64             rootVisible: me.rootVisible,
65             listeners: {
66                 beforeexpand: me.onBeforeExpand,
67                 expand: me.onExpand,
68                 beforecollapse: me.onBeforeCollapse,
69                 collapse: me.onCollapse,
70                 scope: me
71             }
72         });
73         
74         if (me.node) {
75             me.setRootNode(me.node);
76         }
77         me.animQueue = {};
78         me.callParent(arguments);
79     },
80
81     processUIEvent: function(e) {
82         // If the clicked node is part of an animation, ignore the click.
83         // This is because during a collapse animation, the associated Records
84         // will already have been removed from the Store, and the event is not processable.
85         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
86             return false;
87         }
88         return this.callParent(arguments);
89     },
90
91     onClear: function(){
92         this.store.removeAll();    
93     },
94
95     setRootNode: function(node) {
96         var me = this;        
97         me.store.setNode(node);
98         me.node = node;
99         if (!me.rootVisible) {
100             node.expand();
101         }
102     },
103     
104     onRender: function() {
105         var me = this,
106             el;
107
108         me.callParent(arguments);
109
110         el = me.el;
111         el.on({
112             scope: me,
113             delegate: me.expanderSelector,
114             mouseover: me.onExpanderMouseOver,
115             mouseout: me.onExpanderMouseOut
116         });
117         el.on({
118             scope: me,
119             delegate: me.checkboxSelector,
120             click: me.onCheckboxChange
121         });
122     },
123
124     onCheckboxChange: function(e, t) {
125         var me = this,
126             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
127             
128         if (item) {
129             me.onCheckChange(me.getRecord(item));
130         }
131     },
132     
133     onCheckChange: function(record){
134         var checked = record.get('checked');
135         if (Ext.isBoolean(checked)) {
136             checked = !checked;
137             record.set('checked', checked);
138             this.fireEvent('checkchange', record, checked);
139         }
140     },
141
142     getChecked: function() {
143         var checked = [];
144         this.node.cascadeBy(function(rec){
145             if (rec.get('checked')) {
146                 checked.push(rec);
147             }
148         });
149         return checked;
150     },
151     
152     isItemChecked: function(rec){
153         return rec.get('checked');
154     },
155
156     createAnimWrap: function(record, index) {
157         var thHtml = '',
158             headerCt = this.panel.headerCt,
159             headers = headerCt.getGridColumns(),
160             i = 0, len = headers.length, item,
161             node = this.getNode(record),
162             tmpEl, nodeEl;
163
164         for (; i &lt; len; i++) {
165             item = headers[i];
166             thHtml += '&lt;th style=&quot;width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;&quot;&gt;&lt;/th&gt;';
167         }
168
169         nodeEl = Ext.get(node);        
170         tmpEl = nodeEl.insertSibling({
171             tag: 'tr',
172             html: [
173                 '&lt;td colspan=&quot;' + headerCt.getColumnCount() + '&quot;&gt;',
174                     '&lt;div class=&quot;' + this.nodeAnimWrapCls + '&quot;&gt;',
175                         '&lt;table class=&quot;' + Ext.baseCSSPrefix + 'grid-table&quot; style=&quot;width: ' + headerCt.getFullWidth() + 'px;&quot;&gt;&lt;tbody&gt;',
176                             thHtml,
177                         '&lt;/tbody&gt;&lt;/table&gt;',
178                     '&lt;/div&gt;',
179                 '&lt;/td&gt;'
180             ].join('')
181         }, 'after');
182
183         return {
184             record: record,
185             node: node,
186             el: tmpEl,
187             expanding: false,
188             collapsing: false,
189             animating: false,
190             animateEl: tmpEl.down('div'),
191             targetEl: tmpEl.down('tbody')
192         };
193     },
194
195     getAnimWrap: function(parent) {
196         if (!this.animate) {
197             return null;
198         }
199
200         // We are checking to see which parent is having the animation wrap
201         while (parent) {
202             if (parent.animWrap) {
203                 return parent.animWrap;
204             }
205             parent = parent.parentNode;
206         }
207         return null;
208     },
209
210     doAdd: function(nodes, records, index) {
211         // If we are adding records which have a parent that is currently expanding
212         // lets add them to the animation wrap
213         var me = this,
214             record = records[0],
215             parent = record.parentNode,
216             a = me.all.elements,
217             relativeIndex = 0,
218             animWrap = me.getAnimWrap(parent),
219             targetEl, children, len;
220
221         if (!animWrap || !animWrap.expanding) {
222             me.resetScrollers();
223             return me.callParent(arguments);
224         }
225
226         // We need the parent that has the animWrap, not the nodes parent
227         parent = animWrap.record;
228         
229         // If there is an anim wrap we do our special magic logic
230         targetEl = animWrap.targetEl;
231         children = targetEl.dom.childNodes;
232         
233         // We subtract 1 from the childrens length because we have a tr in there with the th'es
234         len = children.length - 1;
235         
236         // The relative index is the index in the full flat collection minus the index of the wraps parent
237         relativeIndex = index - me.indexOf(parent) - 1;
238         
239         // If we are adding records to the wrap that have a higher relative index then there are currently children
240         // it means we have to append the nodes to the wrap
241         if (!len || relativeIndex &gt;= len) {
242             targetEl.appendChild(nodes);
243         }
244         // If there are already more children then the relative index it means we are adding child nodes of
245         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
246         else {
247             // +1 because of the tr with th'es that is already there
248             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
249         }
250
251         // We also have to update the CompositeElementLite collection of the DataView
252         Ext.Array.insert(a, index, nodes);
253         
254         // If we were in an animation we need to now change the animation
255         // because the targetEl just got higher.
256         if (animWrap.isAnimating) {
257             me.onExpand(parent);
258         }
259     },
260     
261     beginBulkUpdate: function(){
262         this.bulkUpdate = true;
263         this.ownerCt.changingScrollbars = true;  
264     },
265     
266     endBulkUpdate: function(){
267         var me = this,
268             ownerCt = me.ownerCt;
269         
270         me.bulkUpdate = false;
271         me.ownerCt.changingScrollbars = true;  
272         me.resetScrollers();  
273     },
274     
275     onRemove : function(ds, record, index) {
276         var me = this,
277             bulk = me.bulkUpdate;
278
279         me.doRemove(record, index);
280         if (!bulk) {
281             me.updateIndexes(index);
282         }
283         if (me.store.getCount() === 0){
284             me.refresh();
285         }
286         if (!bulk) {
287             me.fireEvent('itemremove', record, index);
288         }
289     },
290     
291     doRemove: function(record, index) {
292         // If we are adding records which have a parent that is currently expanding
293         // lets add them to the animation wrap
294         var me = this,
295             parent = record.parentNode,
296             all = me.all,
297             animWrap = me.getAnimWrap(record),
298             node = all.item(index).dom;
299
300         if (!animWrap || !animWrap.collapsing) {
301             me.resetScrollers();
302             return me.callParent(arguments);
303         }
304
305         animWrap.targetEl.appendChild(node);
306         all.removeElement(index);
307     },
308
309     onBeforeExpand: function(parent, records, index) {
310         var me = this,
311             animWrap;
312             
313         if (!me.rendered || !me.animate) {
314             return;
315         }
316
317         if (me.getNode(parent)) {
318             animWrap = me.getAnimWrap(parent);
319             if (!animWrap) {
320                 animWrap = parent.animWrap = me.createAnimWrap(parent);
321                 animWrap.animateEl.setHeight(0);
322             }
323             else if (animWrap.collapsing) {
324                 // If we expand this node while it is still expanding then we
325                 // have to remove the nodes from the animWrap.
326                 animWrap.targetEl.select(me.itemSelector).remove();
327             } 
328             animWrap.expanding = true;
329             animWrap.collapsing = false;
330         }
331     },
332
333     onExpand: function(parent) {
334         var me = this,
335             queue = me.animQueue,
336             id = parent.getId(),
337             animWrap,
338             animateEl, 
339             targetEl,
340             queueItem;        
341         
342         if (me.singleExpand) {
343             me.ensureSingleExpand(parent);
344         }
345         
346         animWrap = me.getAnimWrap(parent);
347
348         if (!animWrap) {
349             me.resetScrollers();
350             return;
351         }
352         
353         animateEl = animWrap.animateEl;
354         targetEl = animWrap.targetEl;
355
356         animateEl.stopAnimation();
357         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
358         queue[id] = true;
359         animateEl.slideIn('t', {
360             duration: me.expandDuration,
361             listeners: {
362                 scope: me,
363                 lastframe: function() {
364                     // Move all the nodes out of the anim wrap to their proper location
365                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
366                     animWrap.el.remove();
367                     me.resetScrollers();
368                     delete animWrap.record.animWrap;
369                     delete queue[id];
370                 }
371             }
372         });
373         
374         animWrap.isAnimating = true;
375     },
376     
377     resetScrollers: function(){
378         if (!this.bulkUpdate) {
379             var panel = this.panel;
380             
381             panel.determineScrollbars();
382             panel.invalidateScroller();
383         }
384     },
385
386     onBeforeCollapse: function(parent, records, index) {
387         var me = this,
388             animWrap;
389             
390         if (!me.rendered || !me.animate) {
391             return;
392         }
393
394         if (me.getNode(parent)) {
395             animWrap = me.getAnimWrap(parent);
396             if (!animWrap) {
397                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
398             }
399             else if (animWrap.expanding) {
400                 // If we collapse this node while it is still expanding then we
401                 // have to remove the nodes from the animWrap.
402                 animWrap.targetEl.select(this.itemSelector).remove();
403             }
404             animWrap.expanding = false;
405             animWrap.collapsing = true;
406         }
407     },
408     
409     onCollapse: function(parent) {
410         var me = this,
411             queue = me.animQueue,
412             id = parent.getId(),
413             animWrap = me.getAnimWrap(parent),
414             animateEl, targetEl;
415
416         if (!animWrap) {
417             me.resetScrollers();
418             return;
419         }
420         
421         animateEl = animWrap.animateEl;
422         targetEl = animWrap.targetEl;
423
424         queue[id] = true;
425         
426         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
427         animateEl.stopAnimation();
428         animateEl.slideOut('t', {
429             duration: me.collapseDuration,
430             listeners: {
431                 scope: me,
432                 lastframe: function() {
433                     animWrap.el.remove();
434                     delete animWrap.record.animWrap;
435                     me.resetScrollers();
436                     delete queue[id];
437                 }             
438             }
439         });
440         animWrap.isAnimating = true;
441     },
442     
443 <span id='Ext-tree-View-method-isAnimating'>    /**
444 </span>     * Checks if a node is currently undergoing animation
445      * @private
446      * @param {Ext.data.Model} node The node
447      * @return {Boolean} True if the node is animating
448      */
449     isAnimating: function(node) {
450         return !!this.animQueue[node.getId()];    
451     },
452     
453     collectData: function(records) {
454         var data = this.callParent(arguments),
455             rows = data.rows,
456             len = rows.length,
457             i = 0,
458             row, record;
459             
460         for (; i &lt; len; i++) {
461             row = rows[i];
462             record = records[i];
463             if (record.get('qtip')) {
464                 row.rowAttr = 'data-qtip=&quot;' + record.get('qtip') + '&quot;';
465                 if (record.get('qtitle')) {
466                     row.rowAttr += ' ' + 'data-qtitle=&quot;' + record.get('qtitle') + '&quot;';
467                 }
468             }
469             if (record.isExpanded()) {
470                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
471             }
472             if (record.isLoading()) {
473                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
474             }
475         }
476         
477         return data;
478     },
479     
480 <span id='Ext-tree-View-method-expand'>    /**
481 </span>     * Expands a record that is loaded in the view.
482      * @param {Ext.data.Model} record The record to expand
483      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
484      * @param {Function} callback (optional) The function to run after the expand is completed
485      * @param {Object} scope (optional) The scope of the callback function.
486      */
487     expand: function(record, deep, callback, scope) {
488         return record.expand(deep, callback, scope);
489     },
490     
491 <span id='Ext-tree-View-method-collapse'>    /**
492 </span>     * Collapses a record that is loaded in the view.
493      * @param {Ext.data.Model} record The record to collapse
494      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
495      * @param {Function} callback (optional) The function to run after the collapse is completed
496      * @param {Object} scope (optional) The scope of the callback function.
497      */
498     collapse: function(record, deep, callback, scope) {
499         return record.collapse(deep, callback, scope);
500     },
501     
502 <span id='Ext-tree-View-method-toggle'>    /**
503 </span>     * Toggles a record between expanded and collapsed.
504      * @param {Ext.data.Model} recordInstance
505      */
506     toggle: function(record) {
507         this[record.isExpanded() ? 'collapse' : 'expand'](record);
508     },
509     
510     onItemDblClick: function(record, item, index) {
511         this.callParent(arguments);
512         if (this.toggleOnDblClick) {
513             this.toggle(record);
514         }
515     },
516     
517     onBeforeItemMouseDown: function(record, item, index, e) {
518         if (e.getTarget(this.expanderSelector, item)) {
519             return false;
520         }
521         return this.callParent(arguments);
522     },
523     
524     onItemClick: function(record, item, index, e) {
525         if (e.getTarget(this.expanderSelector, item)) {
526             this.toggle(record);
527             return false;
528         }
529         return this.callParent(arguments);
530     },
531     
532     onExpanderMouseOver: function(e, t) {
533         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
534     },
535     
536     onExpanderMouseOut: function(e, t) {
537         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
538     },
539     
540 <span id='Ext-tree-View-method-getTreeStore'>    /**
541 </span>     * Gets the base TreeStore from the bound TreePanel.
542      */
543     getTreeStore: function() {
544         return this.panel.store;
545     },    
546     
547     ensureSingleExpand: function(node) {
548         var parent = node.parentNode;
549         if (parent) {
550             parent.eachChild(function(child) {
551                 if (child !== node &amp;&amp; child.isExpanded()) {
552                     child.collapse();
553                 }
554             });
555         }
556     }
557 });</pre>
558 </body>
559 </html>