/javascripts/lib/src/widgets/tree/TreeNodeUI.js
JavaScript | 627 lines | 457 code | 62 blank | 108 comment | 85 complexity | 15a3b67711ae2fec34c3376fea833294 MD5 | raw file
Possible License(s): GPL-3.0
1/*! 2 * Ext JS Library 3.2.1 3 * Copyright(c) 2006-2010 Ext JS, Inc. 4 * licensing@extjs.com 5 * http://www.extjs.com/license 6 */ 7/** 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 12 * tree nodes.<br> 13 * <p> 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> 16 * <p> 17 * This class provides access to the user interface components of an Ext TreeNode, through 18 * {@link Ext.tree.TreeNode#getUI} 19 */ 20Ext.tree.TreeNodeUI = function(node){ 21 this.node = node; 22 this.rendered = false; 23 this.animating = false; 24 this.wasLeaf = true; 25 this.ecc = 'x-tree-ec-icon x-tree-elbow'; 26 this.emptyIcon = Ext.BLANK_IMAGE_URL; 27}; 28 29Ext.tree.TreeNodeUI.prototype = { 30 // private 31 removeChild : function(node){ 32 if(this.rendered){ 33 this.ctNode.removeChild(node.ui.getEl()); 34 } 35 }, 36 37 // private 38 beforeLoad : function(){ 39 this.addClass("x-tree-node-loading"); 40 }, 41 42 // private 43 afterLoad : function(){ 44 this.removeClass("x-tree-node-loading"); 45 }, 46 47 // private 48 onTextChange : function(node, text, oldText){ 49 if(this.rendered){ 50 this.textNode.innerHTML = text; 51 } 52 }, 53 54 // private 55 onDisableChange : function(node, state){ 56 this.disabled = state; 57 if (this.checkbox) { 58 this.checkbox.disabled = state; 59 } 60 if(state){ 61 this.addClass("x-tree-node-disabled"); 62 }else{ 63 this.removeClass("x-tree-node-disabled"); 64 } 65 }, 66 67 // private 68 onSelectedChange : function(state){ 69 if(state){ 70 this.focus(); 71 this.addClass("x-tree-selected"); 72 }else{ 73 //this.blur(); 74 this.removeClass("x-tree-selected"); 75 } 76 }, 77 78 // private 79 onMove : function(tree, node, oldParent, newParent, index, refNode){ 80 this.childIndent = null; 81 if(this.rendered){ 82 var targetNode = newParent.ui.getContainer(); 83 if(!targetNode){//target not rendered 84 this.holder = document.createElement("div"); 85 this.holder.appendChild(this.wrap); 86 return; 87 } 88 var insertBefore = refNode ? refNode.ui.getEl() : null; 89 if(insertBefore){ 90 targetNode.insertBefore(this.wrap, insertBefore); 91 }else{ 92 targetNode.appendChild(this.wrap); 93 } 94 this.node.renderIndent(true, oldParent != newParent); 95 } 96 }, 97 98/** 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 102 */ 103 addClass : function(cls){ 104 if(this.elNode){ 105 Ext.fly(this.elNode).addClass(cls); 106 } 107 }, 108 109/** 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 112 */ 113 removeClass : function(cls){ 114 if(this.elNode){ 115 Ext.fly(this.elNode).removeClass(cls); 116 } 117 }, 118 119 // private 120 remove : function(){ 121 if(this.rendered){ 122 this.holder = document.createElement("div"); 123 this.holder.appendChild(this.wrap); 124 } 125 }, 126 127 // private 128 fireEvent : function(){ 129 return this.node.fireEvent.apply(this.node, arguments); 130 }, 131 132 // private 133 initEvents : function(){ 134 this.node.on("move", this.onMove, this); 135 136 if(this.node.disabled){ 137 this.onDisableChange(this.node, true); 138 } 139 if(this.node.hidden){ 140 this.hide(); 141 } 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, { 146 node: this.node, 147 handles: this.getDDHandles(), 148 isHandle: false 149 }); 150 } 151 }, 152 153 // private 154 getDDHandles : function(){ 155 return [this.iconNode, this.textNode, this.elNode]; 156 }, 157 158/** 159 * Hides this node. 160 */ 161 hide : function(){ 162 this.node.hidden = true; 163 if(this.wrap){ 164 this.wrap.style.display = "none"; 165 } 166 }, 167 168/** 169 * Shows this node. 170 */ 171 show : function(){ 172 this.node.hidden = false; 173 if(this.wrap){ 174 this.wrap.style.display = ""; 175 } 176 }, 177 178 // private 179 onContextMenu : function(e){ 180 if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) { 181 e.preventDefault(); 182 this.focus(); 183 this.fireEvent("contextmenu", this.node, e); 184 } 185 }, 186 187 // private 188 onClick : function(e){ 189 if(this.dropping){ 190 e.stopEvent(); 191 return; 192 } 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); 197 return; 198 }else if(a && e.ctrlKey){ 199 e.stopEvent(); 200 } 201 e.preventDefault(); 202 if(this.disabled){ 203 return; 204 } 205 206 if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){ 207 this.node.toggle(); 208 } 209 210 this.fireEvent("click", this.node, e); 211 }else{ 212 e.stopEvent(); 213 } 214 }, 215 216 // private 217 onDblClick : function(e){ 218 e.preventDefault(); 219 if(this.disabled){ 220 return; 221 } 222 if(this.fireEvent("beforedblclick", this.node, e) !== false){ 223 if(this.checkbox){ 224 this.toggleCheck(); 225 } 226 if(!this.animating && this.node.isExpandable()){ 227 this.node.toggle(); 228 } 229 this.fireEvent("dblclick", this.node, e); 230 } 231 }, 232 233 onOver : function(e){ 234 this.addClass('x-tree-node-over'); 235 }, 236 237 onOut : function(e){ 238 this.removeClass('x-tree-node-over'); 239 }, 240 241 // private 242 onCheckChange : function(){ 243 var checked = this.checkbox.checked; 244 // fix for IE6 245 this.checkbox.defaultChecked = checked; 246 this.node.attributes.checked = checked; 247 this.fireEvent('checkchange', this.node, checked); 248 }, 249 250 // private 251 ecClick : function(e){ 252 if(!this.animating && this.node.isExpandable()){ 253 this.node.toggle(); 254 } 255 }, 256 257 // private 258 startDrop : function(){ 259 this.dropping = true; 260 }, 261 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); 267 }, 268 269 // private 270 expand : function(){ 271 this.updateExpandIcon(); 272 this.ctNode.style.display = ""; 273 }, 274 275 // private 276 focus : function(){ 277 if(!this.node.preventHScroll){ 278 try{this.anchor.focus(); 279 }catch(e){} 280 }else{ 281 try{ 282 var noscroll = this.node.getOwnerTree().getTreeEl().dom; 283 var l = noscroll.scrollLeft; 284 this.anchor.focus(); 285 noscroll.scrollLeft = l; 286 }catch(e){} 287 } 288 }, 289 290/** 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. 294 */ 295 toggleCheck : function(value){ 296 var cb = this.checkbox; 297 if(cb){ 298 cb.checked = (value === undefined ? !cb.checked : value); 299 this.onCheckChange(); 300 } 301 }, 302 303 // private 304 blur : function(){ 305 try{ 306 this.anchor.blur(); 307 }catch(e){} 308 }, 309 310 // private 311 animExpand : function(callback){ 312 var ct = Ext.get(this.ctNode); 313 ct.stopFx(); 314 if(!this.node.isExpandable()){ 315 this.updateExpandIcon(); 316 this.ctNode.style.display = ""; 317 Ext.callback(callback); 318 return; 319 } 320 this.animating = true; 321 this.updateExpandIcon(); 322 323 ct.slideIn('t', { 324 callback : function(){ 325 this.animating = false; 326 Ext.callback(callback); 327 }, 328 scope: this, 329 duration: this.node.ownerTree.duration || .25 330 }); 331 }, 332 333 // private 334 highlight : function(){ 335 var tree = this.node.getOwnerTree(); 336 Ext.fly(this.wrap).highlight( 337 tree.hlColor || "C3DAF9", 338 {endColor: tree.hlBaseColor} 339 ); 340 }, 341 342 // private 343 collapse : function(){ 344 this.updateExpandIcon(); 345 this.ctNode.style.display = "none"; 346 }, 347 348 // private 349 animCollapse : function(callback){ 350 var ct = Ext.get(this.ctNode); 351 ct.enableDisplayMode('block'); 352 ct.stopFx(); 353 354 this.animating = true; 355 this.updateExpandIcon(); 356 357 ct.slideOut('t', { 358 callback : function(){ 359 this.animating = false; 360 Ext.callback(callback); 361 }, 362 scope: this, 363 duration: this.node.ownerTree.duration || .25 364 }); 365 }, 366 367 // private 368 getContainer : function(){ 369 return this.ctNode; 370 }, 371 372/** 373 * Returns the element which encapsulates this node. 374 * @return {HtmlElement} The DOM element. The default implementation uses a <code><li></code>. 375 */ 376 getEl : function(){ 377 return this.wrap; 378 }, 379 380 // private 381 appendDDGhost : function(ghostNode){ 382 ghostNode.appendChild(this.elNode.cloneNode(true)); 383 }, 384 385 // private 386 getDDRepairXY : function(){ 387 return Ext.lib.Dom.getXY(this.iconNode); 388 }, 389 390 // private 391 onRender : function(){ 392 this.render(); 393 }, 394 395 // private 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; 400 401 if(!this.rendered){ 402 this.rendered = true; 403 404 this.renderElements(n, a, targetNode, bulkRender); 405 406 if(a.qtip){ 407 if(this.textNode.setAttributeNS){ 408 this.textNode.setAttributeNS("ext", "qtip", a.qtip); 409 if(a.qtipTitle){ 410 this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle); 411 } 412 }else{ 413 this.textNode.setAttribute("ext:qtip", a.qtip); 414 if(a.qtipTitle){ 415 this.textNode.setAttribute("ext:qtitle", a.qtipTitle); 416 } 417 } 418 }else if(a.qtipCfg){ 419 a.qtipCfg.target = Ext.id(this.textNode); 420 Ext.QuickTips.register(a.qtipCfg); 421 } 422 this.initEvents(); 423 if(!this.node.expanded){ 424 this.updateExpandIcon(true); 425 } 426 }else{ 427 if(bulkRender === true) { 428 targetNode.appendChild(this.wrap); 429 } 430 } 431 }, 432 433 // private 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() : ''; 437 438 var cb = Ext.isBoolean(a.checked), 439 nel, 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>', 449 "</li>"].join(''); 450 451 if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){ 452 this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf); 453 }else{ 454 this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf); 455 } 456 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]; 461 this.ecNode = cs[1]; 462 this.iconNode = cs[2]; 463 var index = 3; 464 if(cb){ 465 this.checkbox = cs[3]; 466 // fix for IE6 467 this.checkbox.defaultChecked = this.checkbox.checked; 468 index++; 469 } 470 this.anchor = cs[index]; 471 this.textNode = cs[index].firstChild; 472 }, 473 474/** 475 * Returns the <a> element that provides focus for the node's UI. 476 * @return {HtmlElement} The DOM anchor element. 477 */ 478 getAnchor : function(){ 479 return this.anchor; 480 }, 481 482/** 483 * Returns the text node. 484 * @return {HtmlNode} The DOM text node. 485 */ 486 getTextEl : function(){ 487 return this.textNode; 488 }, 489 490/** 491 * Returns the icon <img> element. 492 * @return {HtmlElement} The DOM image element. 493 */ 494 getIconEl : function(){ 495 return this.iconNode; 496 }, 497 498/** 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. 502 */ 503 isChecked : function(){ 504 return this.checkbox ? this.checkbox.checked : false; 505 }, 506 507 // private 508 updateExpandIcon : function(){ 509 if(this.rendered){ 510 var n = this.node, 511 c1, 512 c2, 513 cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow", 514 hasChild = n.hasChildNodes(); 515 if(hasChild || n.attributes.expandable){ 516 if(n.expanded){ 517 cls += "-minus"; 518 c1 = "x-tree-node-collapsed"; 519 c2 = "x-tree-node-expanded"; 520 }else{ 521 cls += "-plus"; 522 c1 = "x-tree-node-expanded"; 523 c2 = "x-tree-node-collapsed"; 524 } 525 if(this.wasLeaf){ 526 this.removeClass("x-tree-node-leaf"); 527 this.wasLeaf = false; 528 } 529 if(this.c1 != c1 || this.c2 != c2){ 530 Ext.fly(this.elNode).replaceClass(c1, c2); 531 this.c1 = c1; this.c2 = c2; 532 } 533 }else{ 534 if(!this.wasLeaf){ 535 Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed"); 536 delete this.c1; 537 delete this.c2; 538 this.wasLeaf = true; 539 } 540 } 541 var ecc = "x-tree-ec-icon "+cls; 542 if(this.ecc != ecc){ 543 this.ecNode.className = ecc; 544 this.ecc = ecc; 545 } 546 } 547 }, 548 549 // private 550 onIdChange: function(id){ 551 if(this.rendered){ 552 this.elNode.setAttribute('ext:tree-node-id', id); 553 } 554 }, 555 556 // private 557 getChildIndent : function(){ 558 if(!this.childIndent){ 559 var buf = [], 560 p = this.node; 561 while(p){ 562 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){ 563 if(!p.isLast()) { 564 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />'); 565 } else { 566 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />'); 567 } 568 } 569 p = p.parentNode; 570 } 571 this.childIndent = buf.join(""); 572 } 573 return this.childIndent; 574 }, 575 576 // private 577 renderIndent : function(){ 578 if(this.rendered){ 579 var indent = "", 580 p = this.node.parentNode; 581 if(p){ 582 indent = p.ui.getChildIndent(); 583 } 584 if(this.indentMarkup != indent){ // don't rerender if not required 585 this.indentNode.innerHTML = indent; 586 this.indentMarkup = indent; 587 } 588 this.updateExpandIcon(); 589 } 590 }, 591 592 destroy : function(){ 593 if(this.elNode){ 594 Ext.dd.Registry.unregister(this.elNode.id); 595 } 596 597 Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){ 598 if(this[el]){ 599 Ext.fly(this[el]).remove(); 600 delete this[el]; 601 } 602 }, this); 603 delete this.node; 604 } 605}; 606 607/** 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> 611 * <p> 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> 614 */ 615Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { 616 // private 617 render : function(){ 618 if(!this.rendered){ 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; 623 } 624 }, 625 collapse : Ext.emptyFn, 626 expand : Ext.emptyFn 627});