1 /* 2 * JBoss, Home of Professional Open Source 3 * Copyright ${year}, Red Hat, Inc. and individual contributors 4 * by the @authors tag. See the copyright.txt in the distribution for a 5 * full listing of individual contributors. 6 * 7 * This is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of 10 * the License, or (at your option) any later version. 11 * 12 * This software is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this software; if not, write to the Free 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 21 */ 22 (function($, rf) { 23 24 var NEW_NODE_TOGGLE_STATE = "__NEW_NODE_TOGGLE_STATE"; 25 26 var TRIGGER_NODE_AJAX_UPDATE = "__TRIGGER_NODE_AJAX_UPDATE"; 27 28 var SELECTION_STATE = "__SELECTION_STATE"; 29 30 var TREE_CLASSES = ["rf-tr-nd-colps", "rf-tr-nd-exp"]; 31 32 var TREE_HANDLE_CLASSES = ["rf-trn-hnd-colps", "rf-trn-hnd-exp"]; 33 34 var TREE_ICON_CLASSES = ["rf-trn-ico-colps", "rf-trn-ico-exp"]; 35 36 rf.ui = rf.ui || {}; 37 38 rf.ui.TreeNode = rf.BaseComponent.extendClass({ 39 40 name: "TreeNode", 41 42 init: function (id, commonOptions) { 43 $superTreeNode.constructor.call(this, id); 44 this.__rootElt = $(this.attachToDom()); 45 46 this.__children = new Array(); 47 48 this.__initializeChildren(commonOptions); 49 50 var handlers = (commonOptions.clientEventHandlers || {})[this.getId().substring(commonOptions.treeId.length)] || {}; 51 52 if (handlers.bth) { 53 rf.Event.bind(this.__rootElt, "beforetoggle", new Function("event", handlers.bth)); 54 } 55 56 if (handlers.th) { 57 rf.Event.bind(this.__rootElt, "toggle", new Function("event", handlers.th)); 58 } 59 60 this.__addLastNodeClass(); 61 }, 62 63 destroy: function() { 64 65 if (this.parent) { 66 this.parent.removeChild(this); 67 this.parent = null; 68 } 69 70 this.__clientToggleStateInput = null; 71 72 this.__clearChildren(); 73 74 this.__rootElt = null; 75 76 $superTreeNode.destroy.call(this); 77 }, 78 79 __initializeChildren: function(commonOptions) { 80 var _this = this; 81 this.__rootElt.children(".rf-tr-nd").each(function() { 82 _this.addChild(new rf.ui.TreeNode(this, commonOptions)); 83 }); 84 }, 85 86 __addLastNodeClass: function() { 87 if (this.__rootElt.next("div").length == 0) { 88 this.__rootElt.addClass("rf-tr-nd-last"); 89 } 90 }, 91 92 __getNodeContainer: function() { 93 return this.__rootElt.find(" > .rf-trn:first"); 94 }, 95 96 __getHandle: function() { 97 return this.__getNodeContainer().find(" > .rf-trn-hnd:first"); 98 }, 99 100 __getContent: function() { 101 return this.__getNodeContainer().find(" > .rf-trn-cnt:first"); 102 }, 103 104 __getIcons: function() { 105 return this.__getContent().find(" > .rf-trn-ico"); 106 }, 107 108 getParent: function() { 109 return this.__parent; 110 }, 111 112 setParent: function(newParent) { 113 this.__parent = newParent; 114 }, 115 116 addChild: function(child, idx) { 117 var start; 118 if (typeof idx != 'undefined') { 119 start = idx; 120 } else { 121 start = this.__children.length; 122 } 123 124 this.__children.splice(start, 0, child); 125 child.setParent(this); 126 }, 127 128 removeChild: function(child) { 129 if (this.__children.length) { 130 var idx = this.__children.indexOf(child); 131 if (idx != -1) { 132 var removedChildren = this.__children.splice(idx, 1); 133 if (removedChildren) { 134 for (var i = 0; i < removedChildren.length; i++) { 135 removedChildren[i].setParent(undefined); 136 } 137 } 138 } 139 } 140 }, 141 142 __clearChildren: function() { 143 for (var i = 0; i < this.__children.length; i++) { 144 this.__children[i].setParent(undefined); 145 } 146 147 this.__children = new Array(); 148 }, 149 150 isExpanded: function() { 151 return !this.isLeaf() && this.__rootElt.hasClass("rf-tr-nd-exp"); 152 }, 153 154 isCollapsed: function() { 155 return !this.isLeaf() && this.__rootElt.hasClass("rf-tr-nd-colps"); 156 }, 157 158 isLeaf: function() { 159 return this.__rootElt.hasClass("rf-tr-nd-lf"); 160 }, 161 162 __canBeToggled: function() { 163 return !this.isLeaf() && !this.__rootElt.hasClass("rf-tr-nd-exp-nc") && !this.__loading; 164 }, 165 166 toggle: function() { 167 if (!this.__canBeToggled()) { 168 return; 169 } 170 171 if (this.isCollapsed()) { 172 this.expand(); 173 } else { 174 this.collapse(); 175 } 176 }, 177 178 __updateClientToggleStateInput: function(newState) { 179 if (!this.__clientToggleStateInput) { 180 this.__clientToggleStateInput = $("<input type='hidden' />").appendTo(this.__rootElt) 181 .attr({name: this.getId() + NEW_NODE_TOGGLE_STATE}); 182 } 183 184 this.__clientToggleStateInput.val(newState.toString()); 185 186 }, 187 188 __fireBeforeToggleEvent: function() { 189 return rf.Event.callHandler(this.__rootElt, "beforetoggle"); 190 }, 191 192 __fireToggleEvent: function() { 193 rf.Event.callHandler(this.__rootElt, "toggle"); 194 }, 195 196 __makeLoading: function() { 197 this.__loading = true; 198 this.__getNodeContainer().addClass("rf-trn-ldn"); 199 }, 200 201 __resetLoading: function() { 202 this.__loading = false; 203 this.__getNodeContainer().removeClass("rf-trn-ldn"); 204 }, 205 206 __changeToggleState: function(newState) { 207 if (!this.isLeaf()) { 208 if (newState ^ this.isExpanded()) { 209 210 if (this.__fireBeforeToggleEvent() === false) { 211 return; 212 } 213 214 var tree = this.getTree(); 215 216 switch (tree.getToggleType()) { 217 case 'client': 218 this.__rootElt.addClass(TREE_CLASSES[newState ? 1 : 0]).removeClass(TREE_CLASSES[!newState ? 1 : 0]); 219 this.__getHandle().addClass(TREE_HANDLE_CLASSES[newState ? 1 : 0]).removeClass(TREE_HANDLE_CLASSES[!newState ? 1 : 0]); 220 221 var icons = this.__getIcons(); 222 if (icons.length == 1) { 223 icons.addClass(TREE_ICON_CLASSES[newState ? 1 : 0]).removeClass(TREE_ICON_CLASSES[!newState ? 1 : 0]); 224 } 225 226 this.__updateClientToggleStateInput(newState); 227 this.__fireToggleEvent(); 228 break; 229 230 case 'ajax': 231 case 'server': 232 //TODO - event? 233 tree.__sendToggleRequest(null, this, newState); 234 break; 235 } 236 } 237 } 238 }, 239 240 collapse: function() { 241 this.__changeToggleState(false); 242 }, 243 244 expand: function() { 245 this.__changeToggleState(true); 246 }, 247 248 __setSelected: function(value) { 249 var content = this.__getContent(); 250 if (value) { 251 content.addClass("rf-trn-sel"); 252 } else { 253 content.removeClass("rf-trn-sel"); 254 } 255 256 this.__selected = value; 257 }, 258 259 isSelected: function() { 260 return this.__selected; 261 }, 262 263 getTree: function() { 264 return this.getParent().getTree(); 265 }, 266 267 getId: function() { 268 return this.__rootElt.attr('id'); 269 } 270 271 }); 272 273 // define super class link for TreeNode 274 var $superTreeNode = rf.ui.TreeNode.$super; 275 276 rf.ui.TreeNode.initNodeByAjax = function(nodeId, commonOptions) { 277 var node = $(document.getElementById(nodeId)); 278 279 var opts = commonOptions || {}; 280 281 var parent = node.parent(".rf-tr-nd, .rf-tr"); 282 283 var idx = node.prevAll(".rf-tr-nd").length; 284 285 var parentNode = rf.component(parent[0]); 286 opts.treeId = parentNode.getTree().getId(); 287 288 var newChild = new rf.ui.TreeNode(node[0], opts); 289 parentNode.addChild(newChild, idx); 290 291 var tree = parentNode.getTree(); 292 293 if (tree.getSelection().contains(newChild.getId())) { 294 newChild.__setSelected(true); 295 } 296 }; 297 298 rf.ui.TreeNode.emitToggleEvent = function(nodeId) { 299 var node = document.getElementById(nodeId); 300 if (!node) { 301 return; 302 } 303 304 rf.component(node).__fireToggleEvent(); 305 }; 306 307 var findTree = function(elt) { 308 return rf.component($(elt).closest(".rf-tr")); 309 }; 310 311 var findTreeNode = function(elt) { 312 return rf.component($(elt).closest(".rf-tr-nd")); 313 }; 314 315 var isEventForAnotherTree = function(tree, elt) { 316 return tree != findTree(elt); 317 }; 318 319 rf.ui.Tree = rf.ui.TreeNode.extendClass({ 320 321 name: "Tree", 322 323 init: function (id, options) { 324 this.__treeRootElt = $(rf.getDomElement(id)); 325 326 var commonOptions = {}; 327 commonOptions.clientEventHandlers = options.clientEventHandlers || {}; 328 commonOptions.treeId = id; 329 330 $superTree.constructor.call(this, this.__treeRootElt, commonOptions); 331 332 this.__toggleType = options.toggleType || 'ajax'; 333 this.__selectionType = options.selectionType || 'client'; 334 335 if (options.ajaxSubmitFunction) { 336 this.__ajaxSubmitFunction = new Function("event", "source", "params", "complete", options.ajaxSubmitFunction); 337 } 338 339 if (options.onbeforeselectionchange) { 340 rf.Event.bind(this.__treeRootElt, "beforeselectionchange", new Function("event", options.onbeforeselectionchange)); 341 } 342 343 if (options.onselectionchange) { 344 rf.Event.bind(this.__treeRootElt, "selectionchange", new Function("event", options.onselectionchange)); 345 } 346 347 this.__toggleNodeEvent = options.toggleNodeEvent; 348 if (this.__toggleNodeEvent) { 349 this.__treeRootElt.delegate(".rf-trn", this.__toggleNodeEvent, this, this.__nodeToggleActivated); 350 } 351 if (!this.__toggleNodeEvent || this.__toggleNodeEvent != 'click') { 352 this.__treeRootElt.delegate(".rf-trn-hnd", "click", this, this.__nodeToggleActivated); 353 } 354 355 this.__treeRootElt.delegate(".rf-trn-cnt", "mousedown", this, this.__nodeSelectionActivated); 356 357 this.__findSelectionInput(); 358 this.__selection = new rf.ui.TreeNodeSet(this.__selectionInput.val()); 359 360 $(document).ready($.proxy(this.__updateSelectionFromInput, this)); 361 }, 362 363 __findSelectionInput: function () { 364 this.__selectionInput = $(" > .rf-tr-sel-inp", this.__treeRootElt); 365 }, 366 367 __addLastNodeClass: function() { 368 //stub function overriding parent class method 369 }, 370 371 destroy: function() { 372 if (this.__toggleNodeEvent) { 373 this.__treeRootElt.undelegate(".rf-trn", this.__toggleNodeEvent, this, this.__nodeToggleActivated); 374 } 375 if (!this.__toggleNodeEvent || this.__toggleNodeEvent != 'click') { 376 this.__treeRootElt.undelegate(".rf-trn-hnd", "click", this, this.__nodeToggleActivated); 377 } 378 379 this.__treeRootElt.undelegate(".rf-trn-cnt", "mousedown", this.__nodeSelectionActivated); 380 this.__treeRootElt = null; 381 382 this.__selectionInput = null; 383 this.__ajaxSubmitFunction = null; 384 $superTree.destroy.call(this); 385 }, 386 387 __nodeToggleActivated: function(event) { 388 var theTree = event.data; 389 if (isEventForAnotherTree(theTree, this)) { 390 return; 391 } 392 393 var treeNode = findTreeNode(this); 394 treeNode.toggle(); 395 }, 396 397 __nodeSelectionActivated: function(event) { 398 var theTree = event.data; 399 if (isEventForAnotherTree(theTree, this)) { 400 return; 401 } 402 403 var treeNode = findTreeNode(this); 404 405 if (event.ctrlKey) { 406 theTree.__toggleSelection(treeNode); 407 } else { 408 theTree.__addToSelection(treeNode); 409 } 410 }, 411 412 __sendToggleRequest: function(event, toggleSource, newNodeState) { 413 var toggleSourceId = toggleSource.getId(); 414 415 var clientParams = {}; 416 clientParams[toggleSourceId + NEW_NODE_TOGGLE_STATE] = newNodeState; 417 418 if (this.getToggleType() == 'server') { 419 var form = this.__treeRootElt.closest('form'); 420 rf.submitForm(form, clientParams); 421 } else { 422 toggleSource.__makeLoading(); 423 clientParams[toggleSourceId + TRIGGER_NODE_AJAX_UPDATE] = newNodeState; 424 this.__ajaxSubmitFunction(event, toggleSourceId, clientParams, function() { 425 var treeNode = rf.component(toggleSourceId); 426 if (treeNode) { 427 treeNode.__resetLoading(); 428 } 429 }); 430 } 431 }, 432 433 getToggleType: function() { 434 return this.__toggleType; 435 }, 436 437 getSelectionType: function() { 438 return this.__selectionType; 439 }, 440 441 getTree: function() { 442 return this; 443 }, 444 445 __handleSelectionChange: function(newSelection) { 446 var eventData = { 447 oldSelection: this.getSelection().getNodes(), 448 newSelection: newSelection.getNodes() 449 }; 450 451 if (rf.Event.callHandler(this.__treeRootElt, "beforeselectionchange", eventData) === false) { 452 return; 453 } 454 455 this.__selectionInput.val(newSelection.getNodeString()); 456 457 if (this.getSelectionType() == 'client') { 458 this.__updateSelection(newSelection); 459 } else { 460 this.__ajaxSubmitFunction(null, this.getId()); 461 } 462 }, 463 464 __toggleSelection: function(node) { 465 var newSelection = this.getSelection().cloneAndToggle(node); 466 this.__handleSelectionChange(newSelection); 467 }, 468 469 __addToSelection: function(node) { 470 var newSelection = this.getSelection().cloneAndAdd(node); 471 this.__handleSelectionChange(newSelection); 472 }, 473 474 __updateSelectionFromInput: function() { 475 this.__findSelectionInput(); 476 this.__updateSelection(new rf.ui.TreeNodeSet(this.__selectionInput.val())); 477 }, 478 479 __updateSelection: function(newSelection) { 480 481 var oldSelection = this.getSelection(); 482 483 oldSelection.each(function() { 484 this.__setSelected(false) 485 }); 486 newSelection.each(function() { 487 this.__setSelected(true) 488 }); 489 490 if (oldSelection.getNodeString() != newSelection.getNodeString()) { 491 rf.Event.callHandler(this.__treeRootElt, "selectionchange", { 492 oldSelection: oldSelection.getNodes(), 493 newSelection: newSelection.getNodes() 494 }); 495 } 496 497 this.__selection = newSelection; 498 }, 499 500 getSelection: function() { 501 return this.__selection; 502 }, 503 504 contextMenuAttach: function (menu) { 505 var selector = "[id='" + this.id[0].id + "'] "; 506 selector += (typeof menu.options.targetSelector === 'undefined') 507 ? ".rf-trn-cnt" : menu.options.targetSelector; 508 selector = $.trim(selector); 509 rf.Event.bind(selector, menu.options.showEvent, $.proxy(menu.__showHandler, menu), menu); 510 } 511 512 }); 513 514 // define super class link for Tree 515 var $superTree = rf.ui.Tree.$super; 516 517 rf.ui.TreeNodeSet = function() { 518 this.init.apply(this, arguments); 519 }; 520 521 //TODO - that's a single-node set, implement multi-node support! 522 $.extend(rf.ui.TreeNodeSet.prototype, { 523 524 init: function(nodeId) { 525 this.__nodeId = nodeId; 526 }, 527 528 contains: function(node) { 529 if (node.getId) { 530 return this.__nodeId == node.getId(); 531 } else { 532 return this.__nodeId == node; 533 } 534 }, 535 536 getNodeString: function() { 537 return this.__nodeId; 538 }, 539 540 toString: function() { 541 return this.getNodeString(); 542 }, 543 544 getNodes: function() { 545 if (this.__nodeId) { 546 var node = rf.component(this.__nodeId); 547 if (node) { 548 return [node]; 549 } else { 550 return null; 551 } 552 } 553 554 return []; 555 }, 556 557 cloneAndAdd: function(node) { 558 return new rf.ui.TreeNodeSet(node.getId()); 559 }, 560 561 cloneAndToggle: function(node) { 562 var nodeId; 563 if (this.contains(node)) { 564 nodeId = ""; 565 } else { 566 nodeId = node.getId(); 567 } 568 569 return new rf.ui.TreeNodeSet(nodeId); 570 }, 571 572 each: function(callback) { 573 $.each(this.getNodes() || [], callback); 574 } 575 }); 576 577 }(RichFaces.jQuery, RichFaces));