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