1/** 2 * jQuery org-chart/tree plugin. 3 * 4 * Author: Wes Nolte 5 * http://twitter.com/wesnolte 6 * 7 * Based on the work of Mark Lee 8 * http://www.capricasoftware.co.uk 9 * 10 * Copyright (c) 2011 Wesley Nolte 11 * Dual licensed under the MIT and GPL licenses. 12 * 13 */ 14(function($) { 15 16 $.fn.jOrgChart = function(options) { 17 var opts = $.extend({}, $.fn.jOrgChart.defaults, options); 18 var $appendTo = $(opts.chartElement); 19 20 // build the tree 21 $this = $(this); 22 var $container = $("<div class='" + opts.chartClass + "'/>"); 23 if($this.is("ul")) { 24 buildNode($this.find("li:first"), $container, 0, opts); 25 } 26 else if($this.is("li")) { 27 buildNode($this, $container, 0, opts); 28 } 29 $appendTo.append($container); 30 31 // add drag and drop if enabled 32 if(opts.dragAndDrop){ 33 $('div.node').draggable({ 34 cursor : 'move', 35 distance : 40, 36 helper : 'clone', 37 opacity : 0.8, 38 revert : 'invalid', 39 revertDuration : 100, 40 snap : 'div.node.expanded', 41 snapMode : 'inner', 42 stack : 'div.node' 43 }); 44 45 $('div.node').droppable({ 46 accept : '.node', 47 activeClass : 'drag-active', 48 hoverClass : 'drop-hover' 49 }); 50 51 // Drag start event handler for nodes 52 $('div.node').bind("dragstart", function handleDragStart( event, ui ){ 53 54 var sourceNode = $(this); 55 sourceNode.parentsUntil('.node-container') 56 .find('*') 57 .filter('.node') 58 .droppable('disable'); 59 }); 60 61 // Drag stop event handler for nodes 62 $('div.node').bind("dragstop", function handleDragStop( event, ui ){ 63 64 /* reload the plugin */ 65 $(opts.chartElement).children().remove(); 66 $this.jOrgChart(opts); 67 }); 68 69 // Drop event handler for nodes 70 $('div.node').bind("drop", function handleDropEvent( event, ui ) { 71 72 var targetID = $(this).data("tree-node"); 73 var targetLi = $this.find("li").filter(function() { return $(this).data("tree-node") === targetID; } ); 74 var targetUl = targetLi.children('ul'); 75 76 var sourceID = ui.draggable.data("tree-node"); 77 var sourceLi = $this.find("li").filter(function() { return $(this).data("tree-node") === sourceID; } ); 78 var sourceUl = sourceLi.parent('ul'); 79 80 if (targetUl.length > 0){ 81 targetUl.append(sourceLi); 82 } else { 83 targetLi.append("<ul></ul>"); 84 targetLi.children('ul').append(sourceLi); 85 } 86 87 //Removes any empty lists 88 if (sourceUl.children().length === 0){ 89 sourceUl.remove(); 90 } 91 92 }); // handleDropEvent 93 94 } // Drag and drop 95 }; 96 97 // Option defaults 98 $.fn.jOrgChart.defaults = { 99 chartElement : 'body', 100 depth : -1, 101 chartClass : "jOrgChart", 102 dragAndDrop: false 103 }; 104 105 var nodeCount = 0; 106 // Method that recursively builds the tree 107 function buildNode($node, $appendTo, level, opts) { 108 var $table = $("<table cellpadding='0' cellspacing='0' border='0'/>"); 109 var $tbody = $("<tbody/>"); 110 111 // Construct the node container(s) 112 var $nodeRow = $("<tr/>").addClass("node-cells"); 113 var $nodeCell = $("<td/>").addClass("node-cell").attr("colspan", 2); 114 var $childNodes = $node.children("ul:first").children("li"); 115 var $nodeDiv; 116 117 if($childNodes.length > 1) { 118 $nodeCell.attr("colspan", $childNodes.length * 2); 119 } 120 // Draw the node 121 // Get the contents - any markup except li and ul allowed 122 var $nodeContent = $node.clone() 123 .children("ul,li") 124 .remove() 125 .end() 126 .html(); 127 128 //Increaments the node count which is used to link the source list and the org chart 129 nodeCount++; 130 $node.data("tree-node", nodeCount); 131 $nodeDiv = $("<div>").addClass("node") 132 .data("tree-node", nodeCount) 133 .append($nodeContent); 134 135 // Expand and contract nodes 136 if ($childNodes.length > 0) { 137 $nodeDiv.click(function() { 138 var $this = $(this); 139 var $tr = $this.closest("tr"); 140 141 if($tr.hasClass('contracted')){ 142 $this.css('cursor','n-resize'); 143 $tr.removeClass('contracted').addClass('expanded'); 144 $tr.nextAll("tr").css('visibility', ''); 145 146 // Update the <li> appropriately so that if the tree redraws collapsed/non-collapsed nodes 147 // maintain their appearance 148 $node.removeClass('collapsed'); 149 }else{ 150 $this.css('cursor','s-resize'); 151 $tr.removeClass('expanded').addClass('contracted'); 152 $tr.nextAll("tr").css('visibility', 'hidden'); 153 154 $node.addClass('collapsed'); 155 } 156 }); 157 } 158 159 $nodeCell.append($nodeDiv); 160 $nodeRow.append($nodeCell); 161 $tbody.append($nodeRow); 162 163 if($childNodes.length > 0) { 164 // if it can be expanded then change the cursor 165 $nodeDiv.css('cursor','n-resize'); 166 167 // recurse until leaves found (-1) or to the level specified 168 if(opts.depth == -1 || (level+1 < opts.depth)) { 169 var $downLineRow = $("<tr/>"); 170 var $downLineCell = $("<td/>").attr("colspan", $childNodes.length*2); 171 $downLineRow.append($downLineCell); 172 173 // draw the connecting line from the parent node to the horizontal line 174 $downLine = $("<div></div>").addClass("line down"); 175 $downLineCell.append($downLine); 176 $tbody.append($downLineRow); 177 178 // Draw the horizontal lines 179 var $linesRow = $("<tr/>"); 180 $childNodes.each(function() { 181 var $left = $("<td> </td>").addClass("line left top"); 182 var $right = $("<td> </td>").addClass("line right top"); 183 $linesRow.append($left).append($right); 184 }); 185 186 // horizontal line shouldn't extend beyond the first and last child branches 187 $linesRow.find("td:first") 188 .removeClass("top") 189 .end() 190 .find("td:last") 191 .removeClass("top"); 192 193 $tbody.append($linesRow); 194 var $childNodesRow = $("<tr/>"); 195 $childNodes.each(function() { 196 var $td = $("<td class='node-container'/>"); 197 $td.attr("colspan", 2); 198 // recurse through children lists and items 199 buildNode($(this), $td, level+1, opts); 200 $childNodesRow.append($td); 201 }); 202 203 } 204 $tbody.append($childNodesRow); 205 } 206 207 // any classes on the LI element get copied to the relevant node in the tree 208 // apart from the special 'collapsed' class, which collapses the sub-tree at this point 209 if ($node.attr('class') != undefined) { 210 var classList = $node.attr('class').split(/\s+/); 211 $.each(classList, function(index,item) { 212 if (item == 'collapsed') { 213 $nodeRow.nextAll('tr').css('visibility', 'hidden'); 214 $nodeRow.removeClass('expanded'); 215 $nodeRow.addClass('contracted'); 216 $nodeDiv.css('cursor','s-resize'); 217 } else { 218 $nodeDiv.addClass(item); 219 } 220 }); 221 } 222 223 $table.append($tbody); 224 $appendTo.append($table); 225 226 /* Prevent trees collapsing if a link inside a node is clicked */ 227 $nodeDiv.children('a').click(function(e){ 228 e.stopPropagation(); 229 }); 230 }; 231 232})(jQuery); 233