initial import
[roojs1] / Roo / tree / TreeNodeUI.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.tree.TreeNodeUI
14  * @constructor
15  * @param {Object} node The node to render
16  * The TreeNode UI implementation is separate from the
17  * tree implementation. Unless you are customizing the tree UI,
18  * you should never have to use this directly.
19  */
20 Roo.tree.TreeNodeUI = function(node){
21     this.node = node;
22     this.rendered = false;
23     this.animating = false;
24     this.emptyIcon = Roo.BLANK_IMAGE_URL;
25 };
26
27 Roo.tree.TreeNodeUI.prototype = {
28     removeChild : function(node){
29         if(this.rendered){
30             this.ctNode.removeChild(node.ui.getEl());
31         }
32     },
33
34     beforeLoad : function(){
35          this.addClass("x-tree-node-loading");
36     },
37
38     afterLoad : function(){
39          this.removeClass("x-tree-node-loading");
40     },
41
42     onTextChange : function(node, text, oldText){
43         if(this.rendered){
44             this.textNode.innerHTML = text;
45         }
46     },
47
48     onDisableChange : function(node, state){
49         this.disabled = state;
50         if(state){
51             this.addClass("x-tree-node-disabled");
52         }else{
53             this.removeClass("x-tree-node-disabled");
54         }
55     },
56
57     onSelectedChange : function(state){
58         if(state){
59             this.focus();
60             this.addClass("x-tree-selected");
61         }else{
62             //this.blur();
63             this.removeClass("x-tree-selected");
64         }
65     },
66
67     onMove : function(tree, node, oldParent, newParent, index, refNode){
68         this.childIndent = null;
69         if(this.rendered){
70             var targetNode = newParent.ui.getContainer();
71             if(!targetNode){//target not rendered
72                 this.holder = document.createElement("div");
73                 this.holder.appendChild(this.wrap);
74                 return;
75             }
76             var insertBefore = refNode ? refNode.ui.getEl() : null;
77             if(insertBefore){
78                 targetNode.insertBefore(this.wrap, insertBefore);
79             }else{
80                 targetNode.appendChild(this.wrap);
81             }
82             this.node.renderIndent(true);
83         }
84     },
85
86     addClass : function(cls){
87         if(this.elNode){
88             Roo.fly(this.elNode).addClass(cls);
89         }
90     },
91
92     removeClass : function(cls){
93         if(this.elNode){
94             Roo.fly(this.elNode).removeClass(cls);
95         }
96     },
97
98     remove : function(){
99         if(this.rendered){
100             this.holder = document.createElement("div");
101             this.holder.appendChild(this.wrap);
102         }
103     },
104
105     fireEvent : function(){
106         return this.node.fireEvent.apply(this.node, arguments);
107     },
108
109     initEvents : function(){
110         this.node.on("move", this.onMove, this);
111         var E = Roo.EventManager;
112         var a = this.anchor;
113
114         var el = Roo.fly(a, '_treeui');
115
116         if(Roo.isOpera){ // opera render bug ignores the CSS
117             el.setStyle("text-decoration", "none");
118         }
119
120         el.on("click", this.onClick, this);
121         el.on("dblclick", this.onDblClick, this);
122
123         if(this.checkbox){
124             Roo.EventManager.on(this.checkbox,
125                     Roo.isIE ? 'click' : 'change', this.onCheckChange, this);
126         }
127
128         el.on("contextmenu", this.onContextMenu, this);
129
130         var icon = Roo.fly(this.iconNode);
131         icon.on("click", this.onClick, this);
132         icon.on("dblclick", this.onDblClick, this);
133         icon.on("contextmenu", this.onContextMenu, this);
134         E.on(this.ecNode, "click", this.ecClick, this, true);
135
136         if(this.node.disabled){
137             this.addClass("x-tree-node-disabled");
138         }
139         if(this.node.hidden){
140             this.addClass("x-tree-node-disabled");
141         }
142         var ot = this.node.getOwnerTree();
143         var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
144         if(dd && (!this.node.isRoot || ot.rootVisible)){
145             Roo.dd.Registry.register(this.elNode, {
146                 node: this.node,
147                 handles: this.getDDHandles(),
148                 isHandle: false
149             });
150         }
151     },
152
153     getDDHandles : function(){
154         return [this.iconNode, this.textNode];
155     },
156
157     hide : function(){
158         if(this.rendered){
159             this.wrap.style.display = "none";
160         }
161     },
162
163     show : function(){
164         if(this.rendered){
165             this.wrap.style.display = "";
166         }
167     },
168
169     onContextMenu : function(e){
170         if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
171             e.preventDefault();
172             this.focus();
173             this.fireEvent("contextmenu", this.node, e);
174         }
175     },
176
177     onClick : function(e){
178         if(this.dropping){
179             e.stopEvent();
180             return;
181         }
182         if(this.fireEvent("beforeclick", this.node, e) !== false){
183             if(!this.disabled && this.node.attributes.href){
184                 this.fireEvent("click", this.node, e);
185                 return;
186             }
187             e.preventDefault();
188             if(this.disabled){
189                 return;
190             }
191
192             if(this.node.attributes.singleClickExpand && !this.animating && this.node.hasChildNodes()){
193                 this.node.toggle();
194             }
195
196             this.fireEvent("click", this.node, e);
197         }else{
198             e.stopEvent();
199         }
200     },
201
202     onDblClick : function(e){
203         e.preventDefault();
204         if(this.disabled){
205             return;
206         }
207         if(this.checkbox){
208             this.toggleCheck();
209         }
210         if(!this.animating && this.node.hasChildNodes()){
211             this.node.toggle();
212         }
213         this.fireEvent("dblclick", this.node, e);
214     },
215
216     onCheckChange : function(){
217         var checked = this.checkbox.checked;
218         this.node.attributes.checked = checked;
219         this.fireEvent('checkchange', this.node, checked);
220     },
221
222     ecClick : function(e){
223         if(!this.animating && this.node.hasChildNodes()){
224             this.node.toggle();
225         }
226     },
227
228     startDrop : function(){
229         this.dropping = true;
230     },
231
232     // delayed drop so the click event doesn't get fired on a drop
233     endDrop : function(){
234        setTimeout(function(){
235            this.dropping = false;
236        }.createDelegate(this), 50);
237     },
238
239     expand : function(){
240         this.updateExpandIcon();
241         this.ctNode.style.display = "";
242     },
243
244     focus : function(){
245         if(!this.node.preventHScroll){
246             try{this.anchor.focus();
247             }catch(e){}
248         }else if(!Roo.isIE){
249             try{
250                 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
251                 var l = noscroll.scrollLeft;
252                 this.anchor.focus();
253                 noscroll.scrollLeft = l;
254             }catch(e){}
255         }
256     },
257
258     toggleCheck : function(value){
259         var cb = this.checkbox;
260         if(cb){
261             cb.checked = (value === undefined ? !cb.checked : value);
262         }
263     },
264
265     blur : function(){
266         try{
267             this.anchor.blur();
268         }catch(e){}
269     },
270
271     animExpand : function(callback){
272         var ct = Roo.get(this.ctNode);
273         ct.stopFx();
274         if(!this.node.hasChildNodes()){
275             this.updateExpandIcon();
276             this.ctNode.style.display = "";
277             Roo.callback(callback);
278             return;
279         }
280         this.animating = true;
281         this.updateExpandIcon();
282
283         ct.slideIn('t', {
284            callback : function(){
285                this.animating = false;
286                Roo.callback(callback);
287             },
288             scope: this,
289             duration: this.node.ownerTree.duration || .25
290         });
291     },
292
293     highlight : function(){
294         var tree = this.node.getOwnerTree();
295         Roo.fly(this.wrap).highlight(
296             tree.hlColor || "C3DAF9",
297             {endColor: tree.hlBaseColor}
298         );
299     },
300
301     collapse : function(){
302         this.updateExpandIcon();
303         this.ctNode.style.display = "none";
304     },
305
306     animCollapse : function(callback){
307         var ct = Roo.get(this.ctNode);
308         ct.enableDisplayMode('block');
309         ct.stopFx();
310
311         this.animating = true;
312         this.updateExpandIcon();
313
314         ct.slideOut('t', {
315             callback : function(){
316                this.animating = false;
317                Roo.callback(callback);
318             },
319             scope: this,
320             duration: this.node.ownerTree.duration || .25
321         });
322     },
323
324     getContainer : function(){
325         return this.ctNode;
326     },
327
328     getEl : function(){
329         return this.wrap;
330     },
331
332     appendDDGhost : function(ghostNode){
333         ghostNode.appendChild(this.elNode.cloneNode(true));
334     },
335
336     getDDRepairXY : function(){
337         return Roo.lib.Dom.getXY(this.iconNode);
338     },
339
340     onRender : function(){
341         this.render();
342     },
343
344     render : function(bulkRender){
345         var n = this.node, a = n.attributes;
346         var targetNode = n.parentNode ?
347               n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
348
349         if(!this.rendered){
350             this.rendered = true;
351
352             this.renderElements(n, a, targetNode, bulkRender);
353
354             if(a.qtip){
355                if(this.textNode.setAttributeNS){
356                    this.textNode.setAttributeNS("ext", "qtip", a.qtip);
357                    if(a.qtipTitle){
358                        this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
359                    }
360                }else{
361                    this.textNode.setAttribute("ext:qtip", a.qtip);
362                    if(a.qtipTitle){
363                        this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
364                    }
365                }
366             }else if(a.qtipCfg){
367                 a.qtipCfg.target = Roo.id(this.textNode);
368                 Roo.QuickTips.register(a.qtipCfg);
369             }
370             this.initEvents();
371             if(!this.node.expanded){
372                 this.updateExpandIcon();
373             }
374         }else{
375             if(bulkRender === true) {
376                 targetNode.appendChild(this.wrap);
377             }
378         }
379     },
380
381     renderElements : function(n, a, targetNode, bulkRender){
382         // add some indent caching, this helps performance when rendering a large tree
383         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
384         var t = n.getOwnerTree();
385         var txt = t.renderer ? t.renderer(n.attributes) : Roo.util.Format.htmlEncode(n.text);
386         var tip = t.rendererTip ? t.rendererTip(n.attributes) : txt;
387         var cb = typeof a.checked == 'boolean';
388         var href = a.href ? a.href : Roo.isGecko ? "" : "#";
389         var buf = ['<li class="x-tree-node"><div class="x-tree-node-el ', a.cls,'">',
390             '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
391             '<img src="', this.emptyIcon, '" class="x-tree-ec-icon" />',
392             '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
393             cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : ' />')) : '',
394             '<a hidefocus="on" href="',href,'" tabIndex="1" ',
395              a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", 
396                 '><span unselectable="on" qtip="' , tip ,'">',txt,"</span></a></div>",
397             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
398             "</li>"];
399
400         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
401             this.wrap = Roo.DomHelper.insertHtml("beforeBegin",
402                                 n.nextSibling.ui.getEl(), buf.join(""));
403         }else{
404             this.wrap = Roo.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
405         }
406
407         this.elNode = this.wrap.childNodes[0];
408         this.ctNode = this.wrap.childNodes[1];
409         var cs = this.elNode.childNodes;
410         this.indentNode = cs[0];
411         this.ecNode = cs[1];
412         this.iconNode = cs[2];
413         var index = 3;
414         if(cb){
415             this.checkbox = cs[3];
416             index++;
417         }
418         this.anchor = cs[index];
419         this.textNode = cs[index].firstChild;
420     },
421
422     getAnchor : function(){
423         return this.anchor;
424     },
425
426     getTextEl : function(){
427         return this.textNode;
428     },
429
430     getIconEl : function(){
431         return this.iconNode;
432     },
433
434     isChecked : function(){
435         return this.checkbox ? this.checkbox.checked : false;
436     },
437
438     updateExpandIcon : function(){
439         if(this.rendered){
440             var n = this.node, c1, c2;
441             var cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow";
442             var hasChild = n.hasChildNodes();
443             if(hasChild){
444                 if(n.expanded){
445                     cls += "-minus";
446                     c1 = "x-tree-node-collapsed";
447                     c2 = "x-tree-node-expanded";
448                 }else{
449                     cls += "-plus";
450                     c1 = "x-tree-node-expanded";
451                     c2 = "x-tree-node-collapsed";
452                 }
453                 if(this.wasLeaf){
454                     this.removeClass("x-tree-node-leaf");
455                     this.wasLeaf = false;
456                 }
457                 if(this.c1 != c1 || this.c2 != c2){
458                     Roo.fly(this.elNode).replaceClass(c1, c2);
459                     this.c1 = c1; this.c2 = c2;
460                 }
461             }else{
462                 if(!this.wasLeaf){
463                     Roo.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-leaf");
464                     delete this.c1;
465                     delete this.c2;
466                     this.wasLeaf = true;
467                 }
468             }
469             var ecc = "x-tree-ec-icon "+cls;
470             if(this.ecc != ecc){
471                 this.ecNode.className = ecc;
472                 this.ecc = ecc;
473             }
474         }
475     },
476
477     getChildIndent : function(){
478         if(!this.childIndent){
479             var buf = [];
480             var p = this.node;
481             while(p){
482                 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
483                     if(!p.isLast()) {
484                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
485                     } else {
486                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
487                     }
488                 }
489                 p = p.parentNode;
490             }
491             this.childIndent = buf.join("");
492         }
493         return this.childIndent;
494     },
495
496     renderIndent : function(){
497         if(this.rendered){
498             var indent = "";
499             var p = this.node.parentNode;
500             if(p){
501                 indent = p.ui.getChildIndent();
502             }
503             if(this.indentMarkup != indent){ // don't rerender if not required
504                 this.indentNode.innerHTML = indent;
505                 this.indentMarkup = indent;
506             }
507             this.updateExpandIcon();
508         }
509     }
510 };
511
512 Roo.tree.RootTreeNodeUI = function(){
513     Roo.tree.RootTreeNodeUI.superclass.constructor.apply(this, arguments);
514 };
515 Roo.extend(Roo.tree.RootTreeNodeUI, Roo.tree.TreeNodeUI, {
516     render : function(){
517         if(!this.rendered){
518             var targetNode = this.node.ownerTree.innerCt.dom;
519             this.node.expanded = true;
520             targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
521             this.wrap = this.ctNode = targetNode.firstChild;
522         }
523     },
524     collapse : function(){
525     },
526     expand : function(){
527     }
528 });