PageRenderTime 49ms CodeModel.GetById 22ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/hippo/src/main/webapp/ext/src/widgets/tree/TreeNode.js

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