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