      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));