Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / docs / source / TreeNodeUI.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.tree.TreeNodeUI"></div>/**
16  * @class Ext.tree.TreeNodeUI
17  * This class provides the default UI implementation for Ext TreeNodes.
18  * The TreeNode UI implementation is separate from the
19  * tree implementation, and allows customizing of the appearance of
20  * tree nodes.<br>
21  * <p>
22  * If you are customizing the Tree's user interface, you
23  * may need to extend this class, but you should never need to instantiate this class.<br>
24  * <p>
25  * This class provides access to the user interface components of an Ext TreeNode, through
26  * {@link Ext.tree.TreeNode#getUI}
27  */
28 Ext.tree.TreeNodeUI = Ext.extend(Object, {
29     
30     constructor : function(node){
31         Ext.apply(this, {
32             node: node,
33             rendered: false,
34             animating: false,
35             wasLeaf: true,
36             ecc: 'x-tree-ec-icon x-tree-elbow',
37             emptyIcon: Ext.BLANK_IMAGE_URL    
38         });
39     },
40     
41     // private
42     removeChild : function(node){
43         if(this.rendered){
44             this.ctNode.removeChild(node.ui.getEl());
45         }
46     },
47
48     // private
49     beforeLoad : function(){
50          this.addClass("x-tree-node-loading");
51     },
52
53     // private
54     afterLoad : function(){
55          this.removeClass("x-tree-node-loading");
56     },
57
58     // private
59     onTextChange : function(node, text, oldText){
60         if(this.rendered){
61             this.textNode.innerHTML = text;
62         }
63     },
64     
65     // private
66     onIconClsChange : function(node, cls, oldCls){
67         if(this.rendered){
68             Ext.fly(this.iconNode).replaceClass(oldCls, cls);
69         }
70     },
71     
72     // private
73     onIconChange : function(node, icon){
74         if(this.rendered){
75             //'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
76             var empty = Ext.isEmpty(icon);
77             this.iconNode.src = empty ? this.emptyIcon : icon;
78             Ext.fly(this.iconNode)[empty ? 'removeClass' : 'addClass']('x-tree-node-inline-icon');
79         }
80     },
81     
82     // private
83     onTipChange : function(node, tip, title){
84         if(this.rendered){
85             var hasTitle = Ext.isDefined(title);
86             if(this.textNode.setAttributeNS){
87                 this.textNode.setAttributeNS("ext", "qtip", tip);
88                 if(hasTitle){
89                     this.textNode.setAttributeNS("ext", "qtitle", title);
90                 }
91             }else{
92                 this.textNode.setAttribute("ext:qtip", tip);
93                 if(hasTitle){
94                     this.textNode.setAttribute("ext:qtitle", title);
95                 }
96             }
97         }
98     },
99     
100     // private
101     onHrefChange : function(node, href, target){
102         if(this.rendered){
103             this.anchor.href = this.getHref(href);
104             if(Ext.isDefined(target)){
105                 this.anchor.target = target;
106             }
107         }
108     },
109     
110     // private
111     onClsChange : function(node, cls, oldCls){
112         if(this.rendered){
113             Ext.fly(this.elNode).replaceClass(oldCls, cls);
114         }    
115     },
116
117     // private
118     onDisableChange : function(node, state){
119         this.disabled = state;
120         if (this.checkbox) {
121             this.checkbox.disabled = state;
122         }
123         this[state ? 'addClass' : 'removeClass']('x-tree-node-disabled');
124     },
125
126     // private
127     onSelectedChange : function(state){
128         if(state){
129             this.focus();
130             this.addClass("x-tree-selected");
131         }else{
132             //this.blur();
133             this.removeClass("x-tree-selected");
134         }
135     },
136
137     // private
138     onMove : function(tree, node, oldParent, newParent, index, refNode){
139         this.childIndent = null;
140         if(this.rendered){
141             var targetNode = newParent.ui.getContainer();
142             if(!targetNode){//target not rendered
143                 this.holder = document.createElement("div");
144                 this.holder.appendChild(this.wrap);
145                 return;
146             }
147             var insertBefore = refNode ? refNode.ui.getEl() : null;
148             if(insertBefore){
149                 targetNode.insertBefore(this.wrap, insertBefore);
150             }else{
151                 targetNode.appendChild(this.wrap);
152             }
153             this.node.renderIndent(true, oldParent != newParent);
154         }
155     },
156
157 <div id="method-Ext.tree.TreeNodeUI-addClass"></div>/**
158  * Adds one or more CSS classes to the node's UI element.
159  * Duplicate classes are automatically filtered out.
160  * @param {String/Array} className The CSS class to add, or an array of classes
161  */
162     addClass : function(cls){
163         if(this.elNode){
164             Ext.fly(this.elNode).addClass(cls);
165         }
166     },
167
168 <div id="method-Ext.tree.TreeNodeUI-removeClass"></div>/**
169  * Removes one or more CSS classes from the node's UI element.
170  * @param {String/Array} className The CSS class to remove, or an array of classes
171  */
172     removeClass : function(cls){
173         if(this.elNode){
174             Ext.fly(this.elNode).removeClass(cls);
175         }
176     },
177
178     // private
179     remove : function(){
180         if(this.rendered){
181             this.holder = document.createElement("div");
182             this.holder.appendChild(this.wrap);
183         }
184     },
185
186     // private
187     fireEvent : function(){
188         return this.node.fireEvent.apply(this.node, arguments);
189     },
190
191     // private
192     initEvents : function(){
193         this.node.on("move", this.onMove, this);
194
195         if(this.node.disabled){
196             this.onDisableChange(this.node, true);
197         }
198         if(this.node.hidden){
199             this.hide();
200         }
201         var ot = this.node.getOwnerTree();
202         var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
203         if(dd && (!this.node.isRoot || ot.rootVisible)){
204             Ext.dd.Registry.register(this.elNode, {
205                 node: this.node,
206                 handles: this.getDDHandles(),
207                 isHandle: false
208             });
209         }
210     },
211
212     // private
213     getDDHandles : function(){
214         return [this.iconNode, this.textNode, this.elNode];
215     },
216
217 <div id="method-Ext.tree.TreeNodeUI-hide"></div>/**
218  * Hides this node.
219  */
220     hide : function(){
221         this.node.hidden = true;
222         if(this.wrap){
223             this.wrap.style.display = "none";
224         }
225     },
226
227 <div id="method-Ext.tree.TreeNodeUI-show"></div>/**
228  * Shows this node.
229  */
230     show : function(){
231         this.node.hidden = false;
232         if(this.wrap){
233             this.wrap.style.display = "";
234         }
235     },
236
237     // private
238     onContextMenu : function(e){
239         if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
240             e.preventDefault();
241             this.focus();
242             this.fireEvent("contextmenu", this.node, e);
243         }
244     },
245
246     // private
247     onClick : function(e){
248         if(this.dropping){
249             e.stopEvent();
250             return;
251         }
252         if(this.fireEvent("beforeclick", this.node, e) !== false){
253             var a = e.getTarget('a');
254             if(!this.disabled && this.node.attributes.href && a){
255                 this.fireEvent("click", this.node, e);
256                 return;
257             }else if(a && e.ctrlKey){
258                 e.stopEvent();
259             }
260             e.preventDefault();
261             if(this.disabled){
262                 return;
263             }
264
265             if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
266                 this.node.toggle();
267             }
268
269             this.fireEvent("click", this.node, e);
270         }else{
271             e.stopEvent();
272         }
273     },
274
275     // private
276     onDblClick : function(e){
277         e.preventDefault();
278         if(this.disabled){
279             return;
280         }
281         if(this.fireEvent("beforedblclick", this.node, e) !== false){
282             if(this.checkbox){
283                 this.toggleCheck();
284             }
285             if(!this.animating && this.node.isExpandable()){
286                 this.node.toggle();
287             }
288             this.fireEvent("dblclick", this.node, e);
289         }
290     },
291
292     onOver : function(e){
293         this.addClass('x-tree-node-over');
294     },
295
296     onOut : function(e){
297         this.removeClass('x-tree-node-over');
298     },
299
300     // private
301     onCheckChange : function(){
302         var checked = this.checkbox.checked;
303         // fix for IE6
304         this.checkbox.defaultChecked = checked;
305         this.node.attributes.checked = checked;
306         this.fireEvent('checkchange', this.node, checked);
307     },
308
309     // private
310     ecClick : function(e){
311         if(!this.animating && this.node.isExpandable()){
312             this.node.toggle();
313         }
314     },
315
316     // private
317     startDrop : function(){
318         this.dropping = true;
319     },
320
321     // delayed drop so the click event doesn't get fired on a drop
322     endDrop : function(){
323        setTimeout(function(){
324            this.dropping = false;
325        }.createDelegate(this), 50);
326     },
327
328     // private
329     expand : function(){
330         this.updateExpandIcon();
331         this.ctNode.style.display = "";
332     },
333
334     // private
335     focus : function(){
336         if(!this.node.preventHScroll){
337             try{this.anchor.focus();
338             }catch(e){}
339         }else{
340             try{
341                 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
342                 var l = noscroll.scrollLeft;
343                 this.anchor.focus();
344                 noscroll.scrollLeft = l;
345             }catch(e){}
346         }
347     },
348
349 <div id="method-Ext.tree.TreeNodeUI-toggleCheck"></div>/**
350  * Sets the checked status of the tree node to the passed value, or, if no value was passed,
351  * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
352  * @param {Boolean} value (optional) The new checked status.
353  */
354     toggleCheck : function(value){
355         var cb = this.checkbox;
356         if(cb){
357             cb.checked = (value === undefined ? !cb.checked : value);
358             this.onCheckChange();
359         }
360     },
361
362     // private
363     blur : function(){
364         try{
365             this.anchor.blur();
366         }catch(e){}
367     },
368
369     // private
370     animExpand : function(callback){
371         var ct = Ext.get(this.ctNode);
372         ct.stopFx();
373         if(!this.node.isExpandable()){
374             this.updateExpandIcon();
375             this.ctNode.style.display = "";
376             Ext.callback(callback);
377             return;
378         }
379         this.animating = true;
380         this.updateExpandIcon();
381
382         ct.slideIn('t', {
383            callback : function(){
384                this.animating = false;
385                Ext.callback(callback);
386             },
387             scope: this,
388             duration: this.node.ownerTree.duration || .25
389         });
390     },
391
392     // private
393     highlight : function(){
394         var tree = this.node.getOwnerTree();
395         Ext.fly(this.wrap).highlight(
396             tree.hlColor || "C3DAF9",
397             {endColor: tree.hlBaseColor}
398         );
399     },
400
401     // private
402     collapse : function(){
403         this.updateExpandIcon();
404         this.ctNode.style.display = "none";
405     },
406
407     // private
408     animCollapse : function(callback){
409         var ct = Ext.get(this.ctNode);
410         ct.enableDisplayMode('block');
411         ct.stopFx();
412
413         this.animating = true;
414         this.updateExpandIcon();
415
416         ct.slideOut('t', {
417             callback : function(){
418                this.animating = false;
419                Ext.callback(callback);
420             },
421             scope: this,
422             duration: this.node.ownerTree.duration || .25
423         });
424     },
425
426     // private
427     getContainer : function(){
428         return this.ctNode;
429     },
430
431 <div id="method-Ext.tree.TreeNodeUI-getEl"></div>/**
432  * Returns the element which encapsulates this node.
433  * @return {HtmlElement} The DOM element. The default implementation uses a <code>&lt;li></code>.
434  */
435     getEl : function(){
436         return this.wrap;
437     },
438
439     // private
440     appendDDGhost : function(ghostNode){
441         ghostNode.appendChild(this.elNode.cloneNode(true));
442     },
443
444     // private
445     getDDRepairXY : function(){
446         return Ext.lib.Dom.getXY(this.iconNode);
447     },
448
449     // private
450     onRender : function(){
451         this.render();
452     },
453
454     // private
455     render : function(bulkRender){
456         var n = this.node, a = n.attributes;
457         var targetNode = n.parentNode ?
458               n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
459
460         if(!this.rendered){
461             this.rendered = true;
462
463             this.renderElements(n, a, targetNode, bulkRender);
464
465             if(a.qtip){
466                 this.onTipChange(n, a.qtip, a.qtipTitle);
467             }else if(a.qtipCfg){
468                 a.qtipCfg.target = Ext.id(this.textNode);
469                 Ext.QuickTips.register(a.qtipCfg);
470             }
471             this.initEvents();
472             if(!this.node.expanded){
473                 this.updateExpandIcon(true);
474             }
475         }else{
476             if(bulkRender === true) {
477                 targetNode.appendChild(this.wrap);
478             }
479         }
480     },
481
482     // private
483     renderElements : function(n, a, targetNode, bulkRender){
484         // add some indent caching, this helps performance when rendering a large tree
485         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
486
487         var cb = Ext.isBoolean(a.checked),
488             nel,
489             href = this.getHref(a.href),
490             buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
491             '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
492             '<img alt="" src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
493             '<img alt="" src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
494             cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
495             '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
496              a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
497             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
498             "</li>"].join('');
499
500         if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
501             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
502         }else{
503             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
504         }
505
506         this.elNode = this.wrap.childNodes[0];
507         this.ctNode = this.wrap.childNodes[1];
508         var cs = this.elNode.childNodes;
509         this.indentNode = cs[0];
510         this.ecNode = cs[1];
511         this.iconNode = cs[2];
512         var index = 3;
513         if(cb){
514             this.checkbox = cs[3];
515             // fix for IE6
516             this.checkbox.defaultChecked = this.checkbox.checked;
517             index++;
518         }
519         this.anchor = cs[index];
520         this.textNode = cs[index].firstChild;
521     },
522     
523     /**
524      * @private Gets a normalized href for the node.
525      * @param {String} href
526      */
527     getHref : function(href){
528         return Ext.isEmpty(href) ? (Ext.isGecko ? '' : '#') : href;
529     },
530
531 <div id="method-Ext.tree.TreeNodeUI-getAnchor"></div>/**
532  * Returns the &lt;a> element that provides focus for the node's UI.
533  * @return {HtmlElement} The DOM anchor element.
534  */
535     getAnchor : function(){
536         return this.anchor;
537     },
538
539 <div id="method-Ext.tree.TreeNodeUI-getTextEl"></div>/**
540  * Returns the text node.
541  * @return {HtmlNode} The DOM text node.
542  */
543     getTextEl : function(){
544         return this.textNode;
545     },
546
547 <div id="method-Ext.tree.TreeNodeUI-getIconEl"></div>/**
548  * Returns the icon &lt;img> element.
549  * @return {HtmlElement} The DOM image element.
550  */
551     getIconEl : function(){
552         return this.iconNode;
553     },
554
555 <div id="method-Ext.tree.TreeNodeUI-isChecked"></div>/**
556  * Returns the checked status of the node. If the node was rendered with no
557  * checkbox, it returns false.
558  * @return {Boolean} The checked flag.
559  */
560     isChecked : function(){
561         return this.checkbox ? this.checkbox.checked : false;
562     },
563
564     // private
565     updateExpandIcon : function(){
566         if(this.rendered){
567             var n = this.node,
568                 c1,
569                 c2,
570                 cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow",
571                 hasChild = n.hasChildNodes();
572             if(hasChild || n.attributes.expandable){
573                 if(n.expanded){
574                     cls += "-minus";
575                     c1 = "x-tree-node-collapsed";
576                     c2 = "x-tree-node-expanded";
577                 }else{
578                     cls += "-plus";
579                     c1 = "x-tree-node-expanded";
580                     c2 = "x-tree-node-collapsed";
581                 }
582                 if(this.wasLeaf){
583                     this.removeClass("x-tree-node-leaf");
584                     this.wasLeaf = false;
585                 }
586                 if(this.c1 != c1 || this.c2 != c2){
587                     Ext.fly(this.elNode).replaceClass(c1, c2);
588                     this.c1 = c1; this.c2 = c2;
589                 }
590             }else{
591                 if(!this.wasLeaf){
592                     Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed");
593                     delete this.c1;
594                     delete this.c2;
595                     this.wasLeaf = true;
596                 }
597             }
598             var ecc = "x-tree-ec-icon "+cls;
599             if(this.ecc != ecc){
600                 this.ecNode.className = ecc;
601                 this.ecc = ecc;
602             }
603         }
604     },
605
606     // private
607     onIdChange: function(id){
608         if(this.rendered){
609             this.elNode.setAttribute('ext:tree-node-id', id);
610         }
611     },
612
613     // private
614     getChildIndent : function(){
615         if(!this.childIndent){
616             var buf = [],
617                 p = this.node;
618             while(p){
619                 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
620                     if(!p.isLast()) {
621                         buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
622                     } else {
623                         buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-icon" />');
624                     }
625                 }
626                 p = p.parentNode;
627             }
628             this.childIndent = buf.join("");
629         }
630         return this.childIndent;
631     },
632
633     // private
634     renderIndent : function(){
635         if(this.rendered){
636             var indent = "",
637                 p = this.node.parentNode;
638             if(p){
639                 indent = p.ui.getChildIndent();
640             }
641             if(this.indentMarkup != indent){ // don't rerender if not required
642                 this.indentNode.innerHTML = indent;
643                 this.indentMarkup = indent;
644             }
645             this.updateExpandIcon();
646         }
647     },
648
649     destroy : function(){
650         if(this.elNode){
651             Ext.dd.Registry.unregister(this.elNode.id);
652         }
653
654         Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){
655             if(this[el]){
656                 Ext.fly(this[el]).remove();
657                 delete this[el];
658             }
659         }, this);
660         delete this.node;
661     }
662 });
663
664 <div id="cls-Ext.tree.RootTreeNodeUI"></div>/**
665  * @class Ext.tree.RootTreeNodeUI
666  * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
667  * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
668  * <p>
669  * If you are customizing the Tree's user interface, you
670  * may need to extend this class, but you should never need to instantiate this class.<br>
671  */
672 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
673     // private
674     render : function(){
675         if(!this.rendered){
676             var targetNode = this.node.ownerTree.innerCt.dom;
677             this.node.expanded = true;
678             targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
679             this.wrap = this.ctNode = targetNode.firstChild;
680         }
681     },
682     collapse : Ext.emptyFn,
683     expand : Ext.emptyFn
684 });</pre>    
685 </body>
686 </html>