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     if (attributes.children) {
158         Roo.each(attributes.children, function(c) {
159             this.appendChild(new Roo.tree.TreeNode(c));
160         }, this);
161     }
162     
163 };
164 Roo.extend(Roo.tree.TreeNode, Roo.data.Node, {
165     
166     
167     preventHScroll: true,
168     /**
169      * Returns true if this node is expanded
170      * @return {Boolean}
171      */
172     isExpanded : function(){
173         return this.expanded;
174     },
175
176     /**
177      * Returns the UI object for this node
178      * @return {TreeNodeUI}
179      */
180     getUI : function(){
181         return this.ui;
182     },
183
184     // private override
185     setFirstChild : function(node){
186         var of = this.firstChild;
187         Roo.tree.TreeNode.superclass.setFirstChild.call(this, node);
188         if(this.childrenRendered && of && node != of){
189             of.renderIndent(true, true);
190         }
191         if(this.rendered){
192             this.renderIndent(true, true);
193         }
194     },
195
196     // private override
197     setLastChild : function(node){
198         var ol = this.lastChild;
199         Roo.tree.TreeNode.superclass.setLastChild.call(this, node);
200         if(this.childrenRendered && ol && node != ol){
201             ol.renderIndent(true, true);
202         }
203         if(this.rendered){
204             this.renderIndent(true, true);
205         }
206     },
207
208     // these methods are overridden to provide lazy rendering support
209     // private override
210     appendChild : function(){
211         var node = Roo.tree.TreeNode.superclass.appendChild.apply(this, arguments);
212         if(node && this.childrenRendered){
213             node.render();
214         }
215         this.ui.updateExpandIcon();
216         return node;
217     },
218
219     // private override
220     removeChild : function(node){
221         this.ownerTree.getSelectionModel().unselect(node);
222         Roo.tree.TreeNode.superclass.removeChild.apply(this, arguments);
223         // if it's been rendered remove dom node
224         if(this.childrenRendered){
225             node.ui.remove();
226         }
227         if(this.childNodes.length < 1){
228             this.collapse(false, false);
229         }else{
230             this.ui.updateExpandIcon();
231         }
232         if(!this.firstChild) {
233             this.childrenRendered = false;
234         }
235         return node;
236     },
237
238     // private override
239     insertBefore : function(node, refNode){
240         var newNode = Roo.tree.TreeNode.superclass.insertBefore.apply(this, arguments);
241         if(newNode && refNode && this.childrenRendered){
242             node.render();
243         }
244         this.ui.updateExpandIcon();
245         return newNode;
246     },
247
248     /**
249      * Sets the text for this node
250      * @param {String} text
251      */
252     setText : function(text){
253         var oldText = this.text;
254         this.text = text;
255         this.attributes.text = text;
256         if(this.rendered){ // event without subscribing
257             this.ui.onTextChange(this, text, oldText);
258         }
259         this.fireEvent("textchange", this, text, oldText);
260     },
261
262     /**
263      * Triggers selection of this node
264      */
265     select : function(){
266         this.getOwnerTree().getSelectionModel().select(this);
267     },
268
269     /**
270      * Triggers deselection of this node
271      */
272     unselect : function(){
273         this.getOwnerTree().getSelectionModel().unselect(this);
274     },
275
276     /**
277      * Returns true if this node is selected
278      * @return {Boolean}
279      */
280     isSelected : function(){
281         return this.getOwnerTree().getSelectionModel().isSelected(this);
282     },
283
284     /**
285      * Expand this node.
286      * @param {Boolean} deep (optional) True to expand all children as well
287      * @param {Boolean} anim (optional) false to cancel the default animation
288      * @param {Function} callback (optional) A callback to be called when
289      * expanding this node completes (does not wait for deep expand to complete).
290      * Called with 1 parameter, this node.
291      */
292     expand : function(deep, anim, callback){
293         if(!this.expanded){
294             if(this.fireEvent("beforeexpand", this, deep, anim) === false){
295                 return;
296             }
297             if(!this.childrenRendered){
298                 this.renderChildren();
299             }
300             this.expanded = true;
301             if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
302                 this.ui.animExpand(function(){
303                     this.fireEvent("expand", this);
304                     if(typeof callback == "function"){
305                         callback(this);
306                     }
307                     if(deep === true){
308                         this.expandChildNodes(true);
309                     }
310                 }.createDelegate(this));
311                 return;
312             }else{
313                 this.ui.expand();
314                 this.fireEvent("expand", this);
315                 if(typeof callback == "function"){
316                     callback(this);
317                 }
318             }
319         }else{
320            if(typeof callback == "function"){
321                callback(this);
322            }
323         }
324         if(deep === true){
325             this.expandChildNodes(true);
326         }
327     },
328
329     isHiddenRoot : function(){
330         return this.isRoot && !this.getOwnerTree().rootVisible;
331     },
332
333     /**
334      * Collapse this node.
335      * @param {Boolean} deep (optional) True to collapse all children as well
336      * @param {Boolean} anim (optional) false to cancel the default animation
337      */
338     collapse : function(deep, anim){
339         if(this.expanded && !this.isHiddenRoot()){
340             if(this.fireEvent("beforecollapse", this, deep, anim) === false){
341                 return;
342             }
343             this.expanded = false;
344             if((this.getOwnerTree().animate && anim !== false) || anim){
345                 this.ui.animCollapse(function(){
346                     this.fireEvent("collapse", this);
347                     if(deep === true){
348                         this.collapseChildNodes(true);
349                     }
350                 }.createDelegate(this));
351                 return;
352             }else{
353                 this.ui.collapse();
354                 this.fireEvent("collapse", this);
355             }
356         }
357         if(deep === true){
358             var cs = this.childNodes;
359             for(var i = 0, len = cs.length; i < len; i++) {
360                 cs[i].collapse(true, false);
361             }
362         }
363     },
364
365     // private
366     delayedExpand : function(delay){
367         if(!this.expandProcId){
368             this.expandProcId = this.expand.defer(delay, this);
369         }
370     },
371
372     // private
373     cancelExpand : function(){
374         if(this.expandProcId){
375             clearTimeout(this.expandProcId);
376         }
377         this.expandProcId = false;
378     },
379
380     /**
381      * Toggles expanded/collapsed state of the node
382      */
383     toggle : function(){
384         if(this.expanded){
385             this.collapse();
386         }else{
387             this.expand();
388         }
389     },
390
391     /**
392      * Ensures all parent nodes are expanded
393      */
394     ensureVisible : function(callback){
395         var tree = this.getOwnerTree();
396         tree.expandPath(this.parentNode.getPath(), false, function(){
397             tree.getTreeEl().scrollChildIntoView(this.ui.anchor);
398             Roo.callback(callback);
399         }.createDelegate(this));
400     },
401
402     /**
403      * Expand all child nodes
404      * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
405      */
406     expandChildNodes : function(deep){
407         var cs = this.childNodes;
408         for(var i = 0, len = cs.length; i < len; i++) {
409                 cs[i].expand(deep);
410         }
411     },
412
413     /**
414      * Collapse all child nodes
415      * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
416      */
417     collapseChildNodes : function(deep){
418         var cs = this.childNodes;
419         for(var i = 0, len = cs.length; i < len; i++) {
420                 cs[i].collapse(deep);
421         }
422     },
423
424     /**
425      * Disables this node
426      */
427     disable : function(){
428         this.disabled = true;
429         this.unselect();
430         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
431             this.ui.onDisableChange(this, true);
432         }
433         this.fireEvent("disabledchange", this, true);
434     },
435
436     /**
437      * Enables this node
438      */
439     enable : function(){
440         this.disabled = false;
441         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
442             this.ui.onDisableChange(this, false);
443         }
444         this.fireEvent("disabledchange", this, false);
445     },
446
447     // private
448     renderChildren : function(suppressEvent){
449         if(suppressEvent !== false){
450             this.fireEvent("beforechildrenrendered", this);
451         }
452         var cs = this.childNodes;
453         for(var i = 0, len = cs.length; i < len; i++){
454             cs[i].render(true);
455         }
456         this.childrenRendered = true;
457     },
458
459     // private
460     sort : function(fn, scope){
461         Roo.tree.TreeNode.superclass.sort.apply(this, arguments);
462         if(this.childrenRendered){
463             var cs = this.childNodes;
464             for(var i = 0, len = cs.length; i < len; i++){
465                 cs[i].render(true);
466             }
467         }
468     },
469
470     // private
471     render : function(bulkRender){
472         this.ui.render(bulkRender);
473         if(!this.rendered){
474             this.rendered = true;
475             if(this.expanded){
476                 this.expanded = false;
477                 this.expand(false, false);
478             }
479         }
480     },
481
482     // private
483     renderIndent : function(deep, refresh){
484         if(refresh){
485             this.ui.childIndent = null;
486         }
487         this.ui.renderIndent();
488         if(deep === true && this.childrenRendered){
489             var cs = this.childNodes;
490             for(var i = 0, len = cs.length; i < len; i++){
491                 cs[i].renderIndent(true, refresh);
492             }
493         }
494     }
495 });