Roo/tree/TreeNode.js
[roojs1] / Roo / tree / TreeNode.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.TreeNode
14  * @extends Roo.data.Node
15  * @cfg {String} text The text for this node
16  * @cfg {Boolean} expanded true to start the node expanded
17  * @cfg {Boolean} allowDrag false to make this node undraggable if DD is on (defaults to true)
18  * @cfg {Boolean} allowDrop false if this node cannot be drop on
19  * @cfg {Boolean} disabled true to start the node disabled
20  * @cfg {String} icon The path to an icon for the node. The preferred way to do this
21  * is to use the cls or iconCls attributes and add the icon via a CSS background image.
22  * @cfg {String} cls A css class to be added to the node
23  * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
24  * @cfg {String} href URL of the link used for the node (defaults to #)
25  * @cfg {String} hrefTarget target frame for the link
26  * @cfg {String} qtip An Ext QuickTip for the node
27  * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
28  * @cfg {Boolean} singleClickExpand True for single click expand on this node
29  * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Roo.tree.TreeNodeUI)
30  * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
31  * @cfg {Array} children Array of Children to be added when created..
32   
33  * (defaults to undefined with no checkbox rendered)
34  * @constructor
35  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
36  */
37 Roo.tree.TreeNode = function(attributes)
38 {
39     attributes = attributes || {};
40     if(typeof attributes == "string"){
41         attributes = {text: attributes};
42     }
43     this.childrenRendered = false;
44     this.rendered = false;
45     Roo.tree.TreeNode.superclass.constructor.call(this, attributes);
46     this.expanded = attributes.expanded === true;
47     this.isTarget = attributes.isTarget !== false;
48     this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
49     this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
50
51     /**
52      * Read-only. The text for this node. To change it use setText().
53      * @type String
54      */
55     this.text = attributes.text;
56     /**
57      * True if this node is disabled.
58      * @type Boolean
59      */
60     this.disabled = attributes.disabled === true;
61
62     this.addEvents({
63         /**
64         * @event textchange
65         * Fires when the text for this node is changed
66         * @param {Node} this This node
67         * @param {String} text The new text
68         * @param {String} oldText The old text
69         */
70         "textchange" : true,
71         /**
72         * @event beforeexpand
73         * Fires before this node is expanded, return false to cancel.
74         * @param {Node} this This node
75         * @param {Boolean} deep
76         * @param {Boolean} anim
77         */
78         "beforeexpand" : true,
79         /**
80         * @event beforecollapse
81         * Fires before this node is collapsed, return false to cancel.
82         * @param {Node} this This node
83         * @param {Boolean} deep
84         * @param {Boolean} anim
85         */
86         "beforecollapse" : true,
87         /**
88         * @event expand
89         * Fires when this node is expanded
90         * @param {Node} this This node
91         */
92         "expand" : true,
93         /**
94         * @event disabledchange
95         * Fires when the disabled status of this node changes
96         * @param {Node} this This node
97         * @param {Boolean} disabled
98         */
99         "disabledchange" : true,
100         /**
101         * @event collapse
102         * Fires when this node is collapsed
103         * @param {Node} this This node
104         */
105         "collapse" : true,
106         /**
107         * @event beforeclick
108         * Fires before click processing. Return false to cancel the default action.
109         * @param {Node} this This node
110         * @param {Roo.EventObject} e The event object
111         */
112         "beforeclick":true,
113         /**
114         * @event checkchange
115         * Fires when a node with a checkbox's checked property changes
116         * @param {Node} this This node
117         * @param {Boolean} checked
118         */
119         "checkchange":true,
120         /**
121         * @event click
122         * Fires when this node is clicked
123         * @param {Node} this This node
124         * @param {Roo.EventObject} e The event object
125         */
126         "click":true,
127         /**
128         * @event dblclick
129         * Fires when this node is double clicked
130         * @param {Node} this This node
131         * @param {Roo.EventObject} e The event object
132         */
133         "dblclick":true,
134         /**
135         * @event contextmenu
136         * Fires when this node is right clicked
137         * @param {Node} this This node
138         * @param {Roo.EventObject} e The event object
139         */
140         "contextmenu":true,
141         /**
142         * @event beforechildrenrendered
143         * Fires right before the child nodes for this node are rendered
144         * @param {Node} this This node
145         */
146         "beforechildrenrendered":true
147     });
148
149     var uiClass = this.attributes.uiProvider || Roo.tree.TreeNodeUI;
150
151     /**
152      * Read-only. The UI for this node
153      * @type TreeNodeUI
154      */
155     this.ui = new uiClass(this);
156      
157     
158     
159 };
160 Roo.extend(Roo.tree.TreeNode, Roo.data.Node, {
161     preventHScroll: true,
162     /**
163      * Returns true if this node is expanded
164      * @return {Boolean}
165      */
166     isExpanded : function(){
167         return this.expanded;
168     },
169
170     /**
171      * Returns the UI object for this node
172      * @return {TreeNodeUI}
173      */
174     getUI : function(){
175         return this.ui;
176     },
177
178     // private override
179     setFirstChild : function(node){
180         var of = this.firstChild;
181         Roo.tree.TreeNode.superclass.setFirstChild.call(this, node);
182         if(this.childrenRendered && of && node != of){
183             of.renderIndent(true, true);
184         }
185         if(this.rendered){
186             this.renderIndent(true, true);
187         }
188     },
189
190     // private override
191     setLastChild : function(node){
192         var ol = this.lastChild;
193         Roo.tree.TreeNode.superclass.setLastChild.call(this, node);
194         if(this.childrenRendered && ol && node != ol){
195             ol.renderIndent(true, true);
196         }
197         if(this.rendered){
198             this.renderIndent(true, true);
199         }
200     },
201
202     // these methods are overridden to provide lazy rendering support
203     // private override
204     appendChild : function(){
205         var node = Roo.tree.TreeNode.superclass.appendChild.apply(this, arguments);
206         if(node && this.childrenRendered){
207             node.render();
208         }
209         this.ui.updateExpandIcon();
210         return node;
211     },
212
213     // private override
214     removeChild : function(node){
215         this.ownerTree.getSelectionModel().unselect(node);
216         Roo.tree.TreeNode.superclass.removeChild.apply(this, arguments);
217         // if it's been rendered remove dom node
218         if(this.childrenRendered){
219             node.ui.remove();
220         }
221         if(this.childNodes.length < 1){
222             this.collapse(false, false);
223         }else{
224             this.ui.updateExpandIcon();
225         }
226         if(!this.firstChild) {
227             this.childrenRendered = false;
228         }
229         return node;
230     },
231
232     // private override
233     insertBefore : function(node, refNode){
234         var newNode = Roo.tree.TreeNode.superclass.insertBefore.apply(this, arguments);
235         if(newNode && refNode && this.childrenRendered){
236             node.render();
237         }
238         this.ui.updateExpandIcon();
239         return newNode;
240     },
241
242     /**
243      * Sets the text for this node
244      * @param {String} text
245      */
246     setText : function(text){
247         var oldText = this.text;
248         this.text = text;
249         this.attributes.text = text;
250         if(this.rendered){ // event without subscribing
251             this.ui.onTextChange(this, text, oldText);
252         }
253         this.fireEvent("textchange", this, text, oldText);
254     },
255
256     /**
257      * Triggers selection of this node
258      */
259     select : function(){
260         this.getOwnerTree().getSelectionModel().select(this);
261     },
262
263     /**
264      * Triggers deselection of this node
265      */
266     unselect : function(){
267         this.getOwnerTree().getSelectionModel().unselect(this);
268     },
269
270     /**
271      * Returns true if this node is selected
272      * @return {Boolean}
273      */
274     isSelected : function(){
275         return this.getOwnerTree().getSelectionModel().isSelected(this);
276     },
277
278     /**
279      * Expand this node.
280      * @param {Boolean} deep (optional) True to expand all children as well
281      * @param {Boolean} anim (optional) false to cancel the default animation
282      * @param {Function} callback (optional) A callback to be called when
283      * expanding this node completes (does not wait for deep expand to complete).
284      * Called with 1 parameter, this node.
285      */
286     expand : function(deep, anim, callback){
287         if(!this.expanded){
288             if(this.fireEvent("beforeexpand", this, deep, anim) === false){
289                 return;
290             }
291             if(!this.childrenRendered){
292                 this.renderChildren();
293             }
294             this.expanded = true;
295             if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
296                 this.ui.animExpand(function(){
297                     this.fireEvent("expand", this);
298                     if(typeof callback == "function"){
299                         callback(this);
300                     }
301                     if(deep === true){
302                         this.expandChildNodes(true);
303                     }
304                 }.createDelegate(this));
305                 return;
306             }else{
307                 this.ui.expand();
308                 this.fireEvent("expand", this);
309                 if(typeof callback == "function"){
310                     callback(this);
311                 }
312             }
313         }else{
314            if(typeof callback == "function"){
315                callback(this);
316            }
317         }
318         if(deep === true){
319             this.expandChildNodes(true);
320         }
321     },
322
323     isHiddenRoot : function(){
324         return this.isRoot && !this.getOwnerTree().rootVisible;
325     },
326
327     /**
328      * Collapse this node.
329      * @param {Boolean} deep (optional) True to collapse all children as well
330      * @param {Boolean} anim (optional) false to cancel the default animation
331      */
332     collapse : function(deep, anim){
333         if(this.expanded && !this.isHiddenRoot()){
334             if(this.fireEvent("beforecollapse", this, deep, anim) === false){
335                 return;
336             }
337             this.expanded = false;
338             if((this.getOwnerTree().animate && anim !== false) || anim){
339                 this.ui.animCollapse(function(){
340                     this.fireEvent("collapse", this);
341                     if(deep === true){
342                         this.collapseChildNodes(true);
343                     }
344                 }.createDelegate(this));
345                 return;
346             }else{
347                 this.ui.collapse();
348                 this.fireEvent("collapse", this);
349             }
350         }
351         if(deep === true){
352             var cs = this.childNodes;
353             for(var i = 0, len = cs.length; i < len; i++) {
354                 cs[i].collapse(true, false);
355             }
356         }
357     },
358
359     // private
360     delayedExpand : function(delay){
361         if(!this.expandProcId){
362             this.expandProcId = this.expand.defer(delay, this);
363         }
364     },
365
366     // private
367     cancelExpand : function(){
368         if(this.expandProcId){
369             clearTimeout(this.expandProcId);
370         }
371         this.expandProcId = false;
372     },
373
374     /**
375      * Toggles expanded/collapsed state of the node
376      */
377     toggle : function(){
378         if(this.expanded){
379             this.collapse();
380         }else{
381             this.expand();
382         }
383     },
384
385     /**
386      * Ensures all parent nodes are expanded
387      */
388     ensureVisible : function(callback){
389         var tree = this.getOwnerTree();
390         tree.expandPath(this.parentNode.getPath(), false, function(){
391             tree.getTreeEl().scrollChildIntoView(this.ui.anchor);
392             Roo.callback(callback);
393         }.createDelegate(this));
394     },
395
396     /**
397      * Expand all child nodes
398      * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
399      */
400     expandChildNodes : function(deep){
401         var cs = this.childNodes;
402         for(var i = 0, len = cs.length; i < len; i++) {
403                 cs[i].expand(deep);
404         }
405     },
406
407     /**
408      * Collapse all child nodes
409      * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
410      */
411     collapseChildNodes : function(deep){
412         var cs = this.childNodes;
413         for(var i = 0, len = cs.length; i < len; i++) {
414                 cs[i].collapse(deep);
415         }
416     },
417
418     /**
419      * Disables this node
420      */
421     disable : function(){
422         this.disabled = true;
423         this.unselect();
424         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
425             this.ui.onDisableChange(this, true);
426         }
427         this.fireEvent("disabledchange", this, true);
428     },
429
430     /**
431      * Enables this node
432      */
433     enable : function(){
434         this.disabled = false;
435         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
436             this.ui.onDisableChange(this, false);
437         }
438         this.fireEvent("disabledchange", this, false);
439     },
440
441     // private
442     renderChildren : function(suppressEvent){
443         if(suppressEvent !== false){
444             this.fireEvent("beforechildrenrendered", this);
445         }
446         var cs = this.childNodes;
447         for(var i = 0, len = cs.length; i < len; i++){
448             cs[i].render(true);
449         }
450         this.childrenRendered = true;
451     },
452
453     // private
454     sort : function(fn, scope){
455         Roo.tree.TreeNode.superclass.sort.apply(this, arguments);
456         if(this.childrenRendered){
457             var cs = this.childNodes;
458             for(var i = 0, len = cs.length; i < len; i++){
459                 cs[i].render(true);
460             }
461         }
462     },
463
464     // private
465     render : function(bulkRender){
466         this.ui.render(bulkRender);
467         if(!this.rendered){
468             this.rendered = true;
469             if(this.expanded){
470                 this.expanded = false;
471                 this.expand(false, false);
472             }
473         }
474     },
475
476     // private
477     renderIndent : function(deep, refresh){
478         if(refresh){
479             this.ui.childIndent = null;
480         }
481         this.ui.renderIndent();
482         if(deep === true && this.childrenRendered){
483             var cs = this.childNodes;
484             for(var i = 0, len = cs.length; i < len; i++){
485                 cs[i].renderIndent(true, refresh);
486             }
487         }
488     }
489 });