3 * Copyright(c) 2006-2010 Ext JS, Inc.
5 * http://www.extjs.com/license
8 * @class Ext.tree.TreeNodeUI
9 * This class provides the default UI implementation for Ext TreeNodes.
10 * The TreeNode UI implementation is separate from the
11 * tree implementation, and allows customizing of the appearance of
14 * If you are customizing the Tree's user interface, you
15 * may need to extend this class, but you should never need to instantiate this class.<br>
17 * This class provides access to the user interface components of an Ext TreeNode, through
18 * {@link Ext.tree.TreeNode#getUI}
20 Ext.tree.TreeNodeUI = function(node){
22 this.rendered = false;
23 this.animating = false;
25 this.ecc = 'x-tree-ec-icon x-tree-elbow';
26 this.emptyIcon = Ext.BLANK_IMAGE_URL;
29 Ext.tree.TreeNodeUI.prototype = {
31 removeChild : function(node){
33 this.ctNode.removeChild(node.ui.getEl());
38 beforeLoad : function(){
39 this.addClass("x-tree-node-loading");
43 afterLoad : function(){
44 this.removeClass("x-tree-node-loading");
48 onTextChange : function(node, text, oldText){
50 this.textNode.innerHTML = text;
55 onDisableChange : function(node, state){
56 this.disabled = state;
58 this.checkbox.disabled = state;
61 this.addClass("x-tree-node-disabled");
63 this.removeClass("x-tree-node-disabled");
68 onSelectedChange : function(state){
71 this.addClass("x-tree-selected");
74 this.removeClass("x-tree-selected");
79 onMove : function(tree, node, oldParent, newParent, index, refNode){
80 this.childIndent = null;
82 var targetNode = newParent.ui.getContainer();
83 if(!targetNode){//target not rendered
84 this.holder = document.createElement("div");
85 this.holder.appendChild(this.wrap);
88 var insertBefore = refNode ? refNode.ui.getEl() : null;
90 targetNode.insertBefore(this.wrap, insertBefore);
92 targetNode.appendChild(this.wrap);
94 this.node.renderIndent(true, oldParent != newParent);
99 * Adds one or more CSS classes to the node's UI element.
100 * Duplicate classes are automatically filtered out.
101 * @param {String/Array} className The CSS class to add, or an array of classes
103 addClass : function(cls){
105 Ext.fly(this.elNode).addClass(cls);
110 * Removes one or more CSS classes from the node's UI element.
111 * @param {String/Array} className The CSS class to remove, or an array of classes
113 removeClass : function(cls){
115 Ext.fly(this.elNode).removeClass(cls);
122 this.holder = document.createElement("div");
123 this.holder.appendChild(this.wrap);
128 fireEvent : function(){
129 return this.node.fireEvent.apply(this.node, arguments);
133 initEvents : function(){
134 this.node.on("move", this.onMove, this);
136 if(this.node.disabled){
137 this.onDisableChange(this.node, true);
139 if(this.node.hidden){
142 var ot = this.node.getOwnerTree();
143 var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
144 if(dd && (!this.node.isRoot || ot.rootVisible)){
145 Ext.dd.Registry.register(this.elNode, {
147 handles: this.getDDHandles(),
154 getDDHandles : function(){
155 return [this.iconNode, this.textNode, this.elNode];
162 this.node.hidden = true;
164 this.wrap.style.display = "none";
172 this.node.hidden = false;
174 this.wrap.style.display = "";
179 onContextMenu : function(e){
180 if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
183 this.fireEvent("contextmenu", this.node, e);
188 onClick : function(e){
193 if(this.fireEvent("beforeclick", this.node, e) !== false){
194 var a = e.getTarget('a');
195 if(!this.disabled && this.node.attributes.href && a){
196 this.fireEvent("click", this.node, e);
198 }else if(a && e.ctrlKey){
206 if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
210 this.fireEvent("click", this.node, e);
217 onDblClick : function(e){
222 if(this.fireEvent("beforedblclick", this.node, e) !== false){
226 if(!this.animating && this.node.isExpandable()){
229 this.fireEvent("dblclick", this.node, e);
233 onOver : function(e){
234 this.addClass('x-tree-node-over');
238 this.removeClass('x-tree-node-over');
242 onCheckChange : function(){
243 var checked = this.checkbox.checked;
245 this.checkbox.defaultChecked = checked;
246 this.node.attributes.checked = checked;
247 this.fireEvent('checkchange', this.node, checked);
251 ecClick : function(e){
252 if(!this.animating && this.node.isExpandable()){
258 startDrop : function(){
259 this.dropping = true;
262 // delayed drop so the click event doesn't get fired on a drop
263 endDrop : function(){
264 setTimeout(function(){
265 this.dropping = false;
266 }.createDelegate(this), 50);
271 this.updateExpandIcon();
272 this.ctNode.style.display = "";
277 if(!this.node.preventHScroll){
278 try{this.anchor.focus();
282 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
283 var l = noscroll.scrollLeft;
285 noscroll.scrollLeft = l;
291 * Sets the checked status of the tree node to the passed value, or, if no value was passed,
292 * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
293 * @param {Boolean} value (optional) The new checked status.
295 toggleCheck : function(value){
296 var cb = this.checkbox;
298 cb.checked = (value === undefined ? !cb.checked : value);
299 this.onCheckChange();
311 animExpand : function(callback){
312 var ct = Ext.get(this.ctNode);
314 if(!this.node.isExpandable()){
315 this.updateExpandIcon();
316 this.ctNode.style.display = "";
317 Ext.callback(callback);
320 this.animating = true;
321 this.updateExpandIcon();
324 callback : function(){
325 this.animating = false;
326 Ext.callback(callback);
329 duration: this.node.ownerTree.duration || .25
334 highlight : function(){
335 var tree = this.node.getOwnerTree();
336 Ext.fly(this.wrap).highlight(
337 tree.hlColor || "C3DAF9",
338 {endColor: tree.hlBaseColor}
343 collapse : function(){
344 this.updateExpandIcon();
345 this.ctNode.style.display = "none";
349 animCollapse : function(callback){
350 var ct = Ext.get(this.ctNode);
351 ct.enableDisplayMode('block');
354 this.animating = true;
355 this.updateExpandIcon();
358 callback : function(){
359 this.animating = false;
360 Ext.callback(callback);
363 duration: this.node.ownerTree.duration || .25
368 getContainer : function(){
373 * Returns the element which encapsulates this node.
374 * @return {HtmlElement} The DOM element. The default implementation uses a <code><li></code>.
381 appendDDGhost : function(ghostNode){
382 ghostNode.appendChild(this.elNode.cloneNode(true));
386 getDDRepairXY : function(){
387 return Ext.lib.Dom.getXY(this.iconNode);
391 onRender : function(){
396 render : function(bulkRender){
397 var n = this.node, a = n.attributes;
398 var targetNode = n.parentNode ?
399 n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
402 this.rendered = true;
404 this.renderElements(n, a, targetNode, bulkRender);
407 if(this.textNode.setAttributeNS){
408 this.textNode.setAttributeNS("ext", "qtip", a.qtip);
410 this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
413 this.textNode.setAttribute("ext:qtip", a.qtip);
415 this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
419 a.qtipCfg.target = Ext.id(this.textNode);
420 Ext.QuickTips.register(a.qtipCfg);
423 if(!this.node.expanded){
424 this.updateExpandIcon(true);
427 if(bulkRender === true) {
428 targetNode.appendChild(this.wrap);
434 renderElements : function(n, a, targetNode, bulkRender){
435 // add some indent caching, this helps performance when rendering a large tree
436 this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
438 var cb = Ext.isBoolean(a.checked),
440 href = a.href ? a.href : Ext.isGecko ? "" : "#",
441 buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
442 '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
443 '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
444 '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
445 cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
446 '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
447 a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
448 '<ul class="x-tree-node-ct" style="display:none;"></ul>',
451 if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
452 this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
454 this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
457 this.elNode = this.wrap.childNodes[0];
458 this.ctNode = this.wrap.childNodes[1];
459 var cs = this.elNode.childNodes;
460 this.indentNode = cs[0];
462 this.iconNode = cs[2];
465 this.checkbox = cs[3];
467 this.checkbox.defaultChecked = this.checkbox.checked;
470 this.anchor = cs[index];
471 this.textNode = cs[index].firstChild;
475 * Returns the <a> element that provides focus for the node's UI.
476 * @return {HtmlElement} The DOM anchor element.
478 getAnchor : function(){
483 * Returns the text node.
484 * @return {HtmlNode} The DOM text node.
486 getTextEl : function(){
487 return this.textNode;
491 * Returns the icon <img> element.
492 * @return {HtmlElement} The DOM image element.
494 getIconEl : function(){
495 return this.iconNode;
499 * Returns the checked status of the node. If the node was rendered with no
500 * checkbox, it returns false.
501 * @return {Boolean} The checked flag.
503 isChecked : function(){
504 return this.checkbox ? this.checkbox.checked : false;
508 updateExpandIcon : function(){
513 cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow",
514 hasChild = n.hasChildNodes();
515 if(hasChild || n.attributes.expandable){
518 c1 = "x-tree-node-collapsed";
519 c2 = "x-tree-node-expanded";
522 c1 = "x-tree-node-expanded";
523 c2 = "x-tree-node-collapsed";
526 this.removeClass("x-tree-node-leaf");
527 this.wasLeaf = false;
529 if(this.c1 != c1 || this.c2 != c2){
530 Ext.fly(this.elNode).replaceClass(c1, c2);
531 this.c1 = c1; this.c2 = c2;
535 Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed");
541 var ecc = "x-tree-ec-icon "+cls;
543 this.ecNode.className = ecc;
550 onIdChange: function(id){
552 this.elNode.setAttribute('ext:tree-node-id', id);
557 getChildIndent : function(){
558 if(!this.childIndent){
562 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
564 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
566 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
571 this.childIndent = buf.join("");
573 return this.childIndent;
577 renderIndent : function(){
580 p = this.node.parentNode;
582 indent = p.ui.getChildIndent();
584 if(this.indentMarkup != indent){ // don't rerender if not required
585 this.indentNode.innerHTML = indent;
586 this.indentMarkup = indent;
588 this.updateExpandIcon();
592 destroy : function(){
594 Ext.dd.Registry.unregister(this.elNode.id);
597 Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){
599 Ext.fly(this[el]).remove();
608 * @class Ext.tree.RootTreeNodeUI
609 * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
610 * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
612 * If you are customizing the Tree's user interface, you
613 * may need to extend this class, but you should never need to instantiate this class.<br>
615 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
619 var targetNode = this.node.ownerTree.innerCt.dom;
620 this.node.expanded = true;
621 targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
622 this.wrap = this.ctNode = targetNode.firstChild;
625 collapse : Ext.emptyFn,