2 * jQuery org-chart/tree plugin.
5 * http://twitter.com/wesnolte
7 * Based on the work of Mark Lee
8 * http://www.capricasoftware.co.uk
10 * Copyright (c) 2011 Wesley Nolte
11 * Dual licensed under the MIT and GPL licenses.
16 $.fn.jOrgChart = function(options) {
17 var opts = $.extend({}, $.fn.jOrgChart.defaults, options);
18 var $appendTo = $(opts.chartElement);
22 var $container = $("<div class='" + opts.chartClass + "'/>");
24 buildNode($this.find("li:first"), $container, 0, opts);
26 else if($this.is("li")) {
27 buildNode($this, $container, 0, opts);
29 $appendTo.append($container);
31 // add drag and drop if enabled
33 $('div.node').draggable({
40 snap : 'div.node.expanded',
45 $('div.node').droppable({
47 activeClass : 'drag-active',
48 hoverClass : 'drop-hover'
51 // Drag start event handler for nodes
52 $('div.node').bind("dragstart", function handleDragStart( event, ui ){
54 var sourceNode = $(this);
55 sourceNode.parentsUntil('.node-container')
58 .droppable('disable');
61 // Drag stop event handler for nodes
62 $('div.node').bind("dragstop", function handleDragStop( event, ui ){
64 /* reload the plugin */
65 $(opts.chartElement).children().remove();
66 $this.jOrgChart(opts);
69 // Drop event handler for nodes
70 $('div.node').bind("drop", function handleDropEvent( event, ui ) {
71 var sourceNode = ui.draggable;
72 var targetNode = $(this);
74 // finding nodes based on plaintext and html
76 var targetLi = $('li').filter(function(){
82 var attr = li.attr('id');
83 if (typeof attr !== 'undefined' && attr !== false) {
84 return li.attr("id") == targetNode.attr("id");
87 return li.html() == targetNode.html();
92 var sourceLi = $('li').filter(function(){
98 var attr = li.attr('id');
99 if (typeof attr !== 'undefined' && attr !== false) {
100 return li.attr("id") == sourceNode.attr("id");
103 return li.html() == sourceNode.html();
108 var sourceliClone = sourceLi.clone();
109 var sourceUl = sourceLi.parent('ul');
111 if(sourceUl.children('li').size() > 1){
117 var id = sourceLi.attr("id");
119 if(targetLi.children('ul').size() >0){
120 if (typeof id !== 'undefined' && id !== false) {
121 targetLi.children('ul').append('<li id="'+id+'">'+sourceliClone.html()+'</li>');
123 targetLi.children('ul').append('<li>'+sourceliClone.html()+'</li>');
126 if (typeof id !== 'undefined' && id !== false) {
127 targetLi.append('<ul><li id="'+id+'">'+sourceliClone.html()+'</li></ul>');
129 targetLi.append('<ul><li>'+sourceliClone.html()+'</li></ul>');
133 }); // handleDropEvent
139 $.fn.jOrgChart.defaults = {
140 chartElement : 'body',
142 chartClass : "jOrgChart",
146 // Method that recursively builds the tree
147 function buildNode($node, $appendTo, level, opts) {
148 var $table = $("<table cellpadding='0' cellspacing='0' border='0'/>");
149 var $tbody = $("<tbody/>");
151 // Construct the node container(s)
152 var $nodeRow = $("<tr/>").addClass("node-cells");
153 var $nodeCell = $("<td/>").addClass("node-cell").attr("colspan", 2);
154 var $childNodes = $node.children("ul:first").children("li");
157 if($childNodes.length > 1) {
158 $nodeCell.attr("colspan", $childNodes.length * 2);
161 // Get the contents - any markup except li and ul allowed
162 var $nodeContent = $node.clone()
168 var new_node_id = $node.attr("id");
169 if (typeof new_node_id !== 'undefined' && new_node_id !== false) {
170 $nodeDiv = $("<div>").addClass("node").attr("id", $node.attr("id")).append($nodeContent);
172 $nodeDiv = $("<div>").addClass("node").append($nodeContent);
175 // Expand and contract nodes
176 if ($childNodes.length > 0) {
177 $nodeDiv.click(function() {
179 var $tr = $this.closest("tr");
181 if($tr.hasClass('contracted')){
182 $this.css('cursor','n-resize');
183 $tr.removeClass('contracted').addClass('expanded');
184 $tr.nextAll("tr").css('visibility', '');
186 $this.css('cursor','s-resize');
187 $tr.removeClass('expanded').addClass('contracted');
188 $tr.nextAll("tr").css('visibility', 'hidden');
193 $nodeCell.append($nodeDiv);
194 $nodeRow.append($nodeCell);
195 $tbody.append($nodeRow);
197 if($childNodes.length > 0) {
198 // if it can be expanded then change the cursor
199 $nodeDiv.css('cursor','n-resize').addClass('expanded');
201 // recurse until leaves found (-1) or to the level specified
202 if(opts.depth == -1 || (level+1 < opts.depth)) {
203 var $downLineRow = $("<tr/>");
204 var $downLineCell = $("<td/>").attr("colspan", $childNodes.length*2);
205 $downLineRow.append($downLineCell);
207 // draw the connecting line from the parent node to the horizontal line
208 $downLine = $("<div></div>").addClass("line down");
209 $downLineCell.append($downLine);
210 $tbody.append($downLineRow);
212 // Draw the horizontal lines
213 var $linesRow = $("<tr/>");
214 $childNodes.each(function() {
215 var $left = $("<td> </td>").addClass("line left top");
216 var $right = $("<td> </td>").addClass("line right top");
217 $linesRow.append($left).append($right);
220 // horizontal line shouldn't extend beyond the first and last child branches
221 $linesRow.find("td:first")
227 $tbody.append($linesRow);
228 var $childNodesRow = $("<tr/>");
229 $childNodes.each(function() {
230 var $td = $("<td class='node-container'/>");
231 $td.attr("colspan", 2);
232 // recurse through children lists and items
233 buildNode($(this), $td, level+1, opts);
234 $childNodesRow.append($td);
238 $tbody.append($childNodesRow);
241 // any classes on the LI element get copied to the relevant node in the tree
242 // apart from the special 'collapsed' class, which collapses the sub-tree at this point
243 if ($node.attr('class') != undefined) {
244 var classList = $node.attr('class').split(/\s+/);
245 $.each(classList, function(index,item) {
246 if (item == 'collapsed') {
247 $nodeRow.nextAll('tr').css('display', 'none');
248 $nodeRow.removeClass('expanded');
249 $nodeRow.addClass('contracted');
250 $nodeDiv.css('cursor','s-resize');
252 $nodeDiv.addClass(item);
257 $table.append($tbody);
258 $appendTo.append($table);
260 /* Prevent trees collapsing if a link inside a node is clicked */
261 $nodeDiv.children('a').click(function(e){