diff --git a/examples/css/jquery.orgchart.css b/examples/css/jquery.orgchart.css index a65fba2f..cbbdefb8 100644 --- a/examples/css/jquery.orgchart.css +++ b/examples/css/jquery.orgchart.css @@ -18,7 +18,7 @@ */ .hidden { - display: none; + display: none!important; } .orgchart { diff --git a/examples/css/style.css b/examples/css/style.css index bd6945ad..d0d625a2 100644 --- a/examples/css/style.css +++ b/examples/css/style.css @@ -32,12 +32,14 @@ body { } .home-link { - margin-top: 10px; + margin-top: 20px; + margin-right: 20px; + float: right; } .home-link a { font-size: 36px; - color: #d43f3a; + color: #d43f3a; text-decoration: none; } diff --git a/examples/edit-orgchart/index.html b/examples/edit-orgchart/index.html new file mode 100644 index 00000000..b2efccc7 --- /dev/null +++ b/examples/edit-orgchart/index.html @@ -0,0 +1,47 @@ + + + + + + + + Organization Chart Plugin + + + + + + + + + +
+
+ + + +
+
+ + + + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/examples/edit-orgchart/scripts.js b/examples/edit-orgchart/scripts.js new file mode 100644 index 00000000..30de7153 --- /dev/null +++ b/examples/edit-orgchart/scripts.js @@ -0,0 +1,69 @@ +'use strict'; + +(function($){ + + $(function() { + + var datascource = { + 'name': 'Ball game', + 'relationship': '001', + 'children': [ + { 'name': 'Football', 'relationship': '110' }, + { 'name': 'Basketball', 'relationship': '110' }, + { 'name': 'Volleyball', 'relationship': '110' } + ] + }; + + $('#chart-container').orgchart({ + 'data' : datascource, + 'nodeTitle': 'name', + 'exportButton': true, + 'exportFilename': 'SportsChart', + 'parentNodeSymbol': 'fa-th-large', + 'createNode': function($node, data) { + $node.on('click', function() { + $('#selected-node').val(data.name).data('node', $node); + }); + } + }); + + $('#btn-add-rootnode').on('click', function() { + var rootnodeVal = $('.edit-panel.first').find('.new-node').val().trim(); + if (!rootnodeVal.length) { + alert('Please input value for parent node'); + return; + } + $('#chart-container').orgchart('addParent', $('#chart-container').find('.node:first'), { 'name': rootnodeVal }); + }); + + $('#btn-add-node').on('click', function() { + var rootnodeVal = $('.edit-panel.second').find('.new-node').val().trim(); + var $node = $('#selected-node').data('node'); + if (!rootnodeVal.length) { + alert('Please input value for new node'); + return; + } + if (!$node) { + alert('Please select one node in orgchart'); + return; + } + var nodeType = $('input[name="node-type"]:checked'); + if (!nodeType.length) { + alert('Please select a node type'); + return; + } + if (nodeType.val() === 'siblings') { + $('#chart-container').orgchart('addSiblings', $node, { + 'siblings': [{ 'name': rootnodeVal, 'relationship': '110' }] + }); + } else { + var hasChild = $node.parent().attr('colspan') > 2 ? true : false; + $('#chart-container').orgchart('addChildren', $node, { + 'children': [{ 'name': rootnodeVal, 'relationship': '1' + (hasChild ? 1 : 0) + '0' }] + }); + } + }); + + }); + +})(jQuery); \ No newline at end of file diff --git a/examples/edit-orgchart/style.css b/examples/edit-orgchart/style.css new file mode 100644 index 00000000..dd99d146 --- /dev/null +++ b/examples/edit-orgchart/style.css @@ -0,0 +1,109 @@ +#chart-container { + background-color: #eee; + height: 300px; +} +.orgchart { + background: #fff; +} + +.orgchart .node { + width: 180px; +} + +.orgchart .node .title { + font-size: 24px; + height: 30px; + padding-top: 4px; +} + +.orgchart .node .title .symbol { + margin-top: 1px; +} + +.edit-panel { + position: relative; + left: 10px; + width: calc(100% - 20px); + border-radius: 4px; + float: left; + margin-top: 10px; + padding: 10px; + color: #fff; + font-size: 24px; +} + +.edit-panel input { + font-size: 24px; + width: 180px; +} + +.edit-panel.first { + background-color: #337ab7; + margin-top: 15px; +} + +.edit-panel.second { + background-color: #449d44; +} + +.edit-panel label { + font-weight: bold; +} + +#selected-node, .new-node { + margin-right: 20px; +} + +.edit-panel button { + color: #333; + background-color: #fff; + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 24px; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; +} + +.edit-panel button:hover,.edit-panel button:focus,.edit-panel button:active { + border-color: #eea236; + box-shadow: 0 0 10px #eea236; +} + +#new-nodelist { + display: inline-block; + list-style:none; + margin: 0; + padding: 0; +} + +#node-type-panel { + position: relative; + top: 5px; +} + +#node-type-panel input[type='radio'] { + display: inline-block; + height: 28px; + width: 28px; +} + +#node-type-panel input[type='radio']+label { + vertical-align: super; +} + +#btn-add-node { + margin-left: 20px; +} \ No newline at end of file diff --git a/examples/js/jquery.orgchart.js b/examples/js/jquery.orgchart.js index b8338f1d..b008e3d4 100644 --- a/examples/js/jquery.orgchart.js +++ b/examples/js/jquery.orgchart.js @@ -31,18 +31,18 @@ 'parentNodeSymbol': 'fa-users' }; - var opts = $.extend(defaultOptions, options); - this.data('orgchart', { 'options' : opts }); - switch (options) { - case 'buildNode': - return buildNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildChildNode': - return buildChildNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildParentNode': - return buildParentNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildSiblingNode': - return buildSiblingNode.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'buildHierarchy': + return buildHierarchy.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addChildren': + return addChildren.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addParent': + return addParent.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addSiblings': + return addSiblings.apply(this, Array.prototype.splice.call(arguments, 1)); + default: // initiation time + var opts = $.extend(defaultOptions, options); + this.data('orgchart', { 'options' : opts }); } // build the org-chart @@ -57,7 +57,7 @@ } }); if ($.type(data) === 'object') { - buildNode(data, $chart, 0, opts); + buildHierarchy($chart, data, 0, opts); } else { $.ajax({ 'url': data, @@ -67,7 +67,7 @@ } }) .done(function(data, textStatus, jqXHR) { - buildNode(data, $chart, 0, opts); + buildHierarchy($chart, data, 0, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { console.log(errorThrown); @@ -109,40 +109,18 @@ return $chartContainer; }; - // determin whether the parent node of the specified node is visible on current chart view - function getParentState($node) { - if ($node.children('.spinner').length > 0) { - return {}; - } - var $parent = $node.closest('table').parent(); - if ($parent.is('td')) { - if ($parent.closest('tr').siblings().is(':visible')) { - return {"exist": true, "visible": true}; - } - return {"exist": true, "visible": false}; - } - return {"exist": false, "visible": false}; - } - function getChildrenState($node) { - if ($node.children('.spinner').length > 0) { - return {}; - } - var $children = $node.closest('tr').siblings(); - if ($children.length > 0) { - if ($children.is(':visible')) { - return {"exist": true, "visible": true}; - } - return {"exist": true, "visible": false}; - } - return {"exist": false, "visible": false}; - } - function getSiblingsState($node) { - if ($node.children('.spinner').length > 0) { - return {}; + // detect the exist/display state of related node + function getNodeState($node, relation) { + var $target = {}; + if (relation === 'parent') { + $target = $node.closest('table').parent().closest('tr').siblings(); + } else if (relation === 'children') { + $target = $node.closest('tr').siblings(); + } else { + $target = $node.closest('table').parent().siblings(); } - var $siblings = $node.closest('table').parent('td').siblings(); - if ($siblings.length > 0) { - if ($siblings.is(':visible')) { + if ($target.length) { + if ($target.is(':visible')) { return {"exist": true, "visible": true}; } return {"exist": true, "visible": false}; @@ -152,34 +130,33 @@ // recursively hide the ancestor node and sibling nodes of the specified node function hideAncestorsSiblings($node, dtd) { - var $nodeContainer = $node.closest('table').parent(); - if ($nodeContainer.parent().siblings('.node-cells').find('.spinner').length > 0) { + if ($node.closest('table').closest('tr').siblings(':first').find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } // firstly, hide the sibling nodes - if (getSiblingsState($node).visible) { + if (getNodeState($node, 'siblings').visible) { hideSiblings($node, false); } // hide the links - var $temp = $nodeContainer.parent().siblings(); + var $temp = $node.closest('table').closest('tr').siblings(); var $links = $temp.slice(1); $links.css('visibility', 'hidden'); // secondly, hide the superior nodes with animation var nodeOffset = $links.eq(0).outerHeight() + $links.eq(1).outerHeight(); var $parent = $temp.eq(0).find('div.node'); - var grandfatherVisible = getParentState($parent).visible; - if ($parent.length > 0 && $parent.is(':visible')) { + var grandfatherVisible = getNodeState($parent, 'parent').visible; + if ($parent.length && $parent.is(':visible')) { $parent.animate({'opacity': 0, 'top': +nodeOffset}, 300, function() { $parent.removeAttr('style'); $links.removeAttr('style'); - $temp.hide(); + $temp.addClass('hidden'); if ($parent.closest('table').parent().is('.orgchart') || !grandfatherVisible) { dtd.resolve(); } }); } // if the current node has the parent node, hide it recursively - if ($parent.length > 0 && grandfatherVisible) { + if ($parent.length && grandfatherVisible) { hideAncestorsSiblings($parent, dtd); } @@ -190,10 +167,10 @@ function showAncestorsSiblings($node) { var dtd = $.Deferred(); // just show only one superior level - var $temp = $node.closest('table').closest('tr').siblings().show(); + var $temp = $node.closest('table').closest('tr').siblings().removeClass('hidden'); // just show only one link - $temp.eq(2).children().slice(1, $temp.eq(2).children().length - 1).hide(); + $temp.eq(2).children().slice(1, $temp.eq(2).children().length - 1).addClass('hidden'); dtd.resolve(); // show the the only parent node with animation $temp.eq(0).find('div.node') @@ -208,7 +185,7 @@ // recursively hide the descendant nodes of the specified node function hideDescendants($node) { var dtd = $.Deferred(); - if ($node.closest('tr').siblings(':last').find('.spinner').length > 0) { + if ($node.closest('tr').siblings(':last').find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } var $links = $node.closest('tr').siblings(':lt(2)'); @@ -220,7 +197,7 @@ ) .done(function() { $links.removeAttr('style'); - $node.closest('tr').siblings().hide(); + $node.closest('tr').siblings().addClass('hidden'); dtd.resolve(); }); @@ -231,7 +208,7 @@ function showDescendants($node) { var dtd = $.Deferred(); // firstly, just show the only one inferior level of the child nodes - var $temp = $node.closest('tr').siblings().show(); + var $temp = $node.closest('tr').siblings().removeClass('hidden'); dtd.resolve(); // secondly, display the child nodes with animation var isLeaf = $temp.eq(2).children('td').length > 1 ? true : false; @@ -245,7 +222,7 @@ }); // lastly, remember to hide all the inferior nodes of child nodes of current node $children.each(function(index, child){ - $(child).closest('tr').siblings().hide(); + $(child).closest('tr').siblings().addClass('hidden'); }); return dtd.promise(); @@ -258,7 +235,7 @@ .animate({'opacity': 0, 'left': offset}, 300) ) .done(function() { - $siblings.hide(); + $siblings.addClass('hidden'); if (justSiblings) { $siblings.closest('.orgchart').css('opacity', 0);// hack for firefox } @@ -275,7 +252,7 @@ function hideSiblings($node, justSiblings) { var dtd = $.Deferred(); var $nodeContainer = $node.closest('table').parent(); - if ($nodeContainer.siblings().find('.spinner').length > 0) { + if ($nodeContainer.siblings().find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } var nodeOffset = $node.outerWidth(); @@ -283,7 +260,7 @@ var $upperLink = $nodeContainer.parent().prev().prev(); $upperLink.css('visibility', 'hidden') var $temp = $nodeContainer.parent().prev().children(); - $temp.slice(1, $temp.length -1).hide(); + $temp.slice(1, $temp.length -1).addClass('hidden'); $temp.eq(0).css('visibility', 'hidden'); $temp.eq($temp.length - 1).css('visibility', 'hidden'); // secondly, hide the sibling nodes with animation simultaneously @@ -297,18 +274,18 @@ function showSiblings($node) { var dtd = $.Deferred(); // firstly, show the sibling td tags - var $siblings = $node.closest('table').parent().siblings().show(); + var $siblings = $node.closest('table').parent().siblings().removeClass('hidden'); // secondly, show the links var $parent = $node.closest('table').closest('tr').siblings(); var $lowerLinks = $parent.eq(2).children(); - $lowerLinks.slice(1, $lowerLinks.length -1).show(); + $lowerLinks.slice(1, $lowerLinks.length -1).removeClass('hidden'); // thirdly, do some cleaning stuff if ($node.children('.topEdge').data('parentState').visible) { $siblings.each(function(index, sibling){ - $(sibling).find('div.node').closest('tr').siblings().hide(); + $(sibling).find('div.node').closest('tr').siblings().addClass('hidden'); }); } else { - $parent.show(); + $parent.removeClass('hidden'); } dtd.resolve(); // lastly, show the sibling nodes with animation @@ -324,7 +301,7 @@ return false; } - $arrow.hide(); + $arrow.addClass('hidden'); $node.append(''); $node.children().not('.spinner').css('opacity', 0.2); $chart.data('inAjax', true); @@ -335,7 +312,7 @@ // terminate loading status for requesting new nodes function endLoadingStatus($arrow, $node, options) { var $chart = $node.closest('div.orgchart'); - $arrow.show(); + $arrow.removeClass('hidden'); $node.find('.spinner').remove(); $node.children().removeAttr('style'); $chart.data('inAjax', false); @@ -374,6 +351,7 @@ // create node function createNode(nodeData, opts) { + var dtd = $.Deferred(); // construct the content of node var $nodeDiv = $('
', { 'id': nodeData[opts.nodeId] }) .addClass('node') @@ -404,7 +382,7 @@ var temp; if (event.type === 'mouseenter') { if ($topEdge.length) { - temp = getParentState($node); + temp = getNodeState($node, 'parent'); if (!$.isEmptyObject(temp)) { $topEdge.data('parentState', temp); } @@ -415,7 +393,7 @@ } } if ($bottomEdge.length) { - temp = getChildrenState($node); + temp = getNodeState($node, 'children'); if (!$.isEmptyObject(temp)) { $bottomEdge.data('childrenState', temp); } @@ -426,7 +404,7 @@ } } if ($leftEdge.length) { - temp = getSiblingsState($node); + temp = getNodeState($node, 'siblings'); if (!$.isEmptyObject(temp)) { $rightEdge.data('siblingsState', temp); $leftEdge.data('siblingsState', temp); @@ -451,11 +429,11 @@ }); // define click event handler for the top edge - $nodeDiv.children('.topEdge').on('click', function(event) { + $nodeDiv.on('click', '.topEdge', function(event) { var $that = $(this); var $node = $that.parent(); var parentState = $that.data('parentState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (parentState.exist) { @@ -468,7 +446,7 @@ $.when(hideAncestorsSiblings($node, dtd))    .done(function(){ parentState.visible = false; - if ($node.children('.leftEdge').length > 0) { + if ($node.children('.leftEdge').length) { $node.children('.leftEdge').data('siblingsState').visible = false; $node.children('.rightEdge').data('siblingsState').visible = false; } @@ -498,25 +476,18 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { + if ($node.closest('div.orgchart').data('inAjax')) { if (!$.isEmptyObject(data)) { - $.when(buildParentNode(data, $that.closest('table'), opts)) -   .done(function(){ - parentState.visible = true; - if (isInAction($node)) { - switchUpDownArrow($that); - } - }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + addParent($node, data, opts); } parentState.exist = true; } - // terminate the loading status - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get parent node data'); parentState.exist = true; + }) + .always(function() { // terminate the loading status endLoadingStatus($that, $node, opts); }); @@ -529,7 +500,7 @@ var $that = $(this); var $node = $that.parent(); var childrenState = $that.data('childrenState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (childrenState.exist) { @@ -564,31 +535,24 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { - if (data.children.length !== 0) { - var dtd = $.Deferred(); - var count = 0; - $.when(buildChildNode(data, $that.closest('tbody'), false, opts, function() { - if (++count === data.children.length + 1) { - dtd.resolve(); - return dtd.promise(); - } - })) -   .done(function(){ - childrenState.visible = true; - if (isInAction($node)) { - switchUpDownArrow($that); - } + if ($node.closest('div.orgchart').data('inAjax')) { + if (data.children.length) { + $.when(addChildren($node, data, opts)) + .done(function() { + childrenState.exist = true; + endLoadingStatus($that, $node, opts); }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + .fail(function() { + console.log('Failed to add children nodes'); + }); } - childrenState.exist = true; } - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get children nodes data'); childrenState.exist = true; + }) + .always(function() { endLoadingStatus($that, $node, opts); }); } @@ -596,11 +560,11 @@ }); // bind click event handler for the left and right edges - $nodeDiv.children('.leftEdge, .rightEdge').on('click', function(event) { + $nodeDiv.on('click', '.leftEdge, .rightEdge', function(event) { var $that = $(this); var $node = $that.parent(); var siblingsState = $that.data('siblingsState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (siblingsState.exist) { @@ -640,16 +604,9 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { + if ($node.closest('div.orgchart').data('inAjax')) { if (data.siblings || data.children) { - $.when(buildSiblingNode(data, $that.closest('table'), opts)) -   .done(function(){ - siblingsState.visible = true; - if (isInAction($node)) { - collapseArrow($node); - } - }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + addSiblings($node, data, opts); } $node.children('.topEdge').data('parentState').exist = true; siblingsState.exist = true; @@ -659,11 +616,12 @@ $that.siblings('.leftEdge').data('siblingsState', {'exist': true, 'visible': true}); } } - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get sibling nodes data'); siblingsState.exist = true; + }) + .always(function() { endLoadingStatus($that, $node, opts); }); } @@ -673,7 +631,7 @@ $nodeDiv.children('.leftEdge').on('mouseenter mouseleave', function(event) { if (event.type === 'mouseenter') { var $rightEdge = $(this).siblings('.rightEdge'); - if (!getSiblingsState($(this)).visible) { + if (!getNodeState($(this), 'siblings').visible) { $rightEdge.addClass('rightEdgeMoveRight'); } else { $rightEdge.addClass('rightEdgeMoveLeft'); @@ -687,252 +645,168 @@ if (opts.createNode) { opts.createNode($nodeDiv, nodeData); } - - return $nodeDiv; + dtd.resolve($nodeDiv); + return dtd.promise(); } // recursively build the tree - function buildNode (nodeData, $appendTo, level, opts, callback) { - var $table = $(''); - var $tbody = $(''); - + function buildHierarchy ($appendTo, nodeData, level, opts, callback) { + var $table; // Construct the node - var $nodeRow = $("").addClass("node-cells"); - var $nodeCell = $(""); - var $downLineCell = $("'); - $.each($childNodes, function() { - var $left = $(''); - var $right = $(''); - $linesRow.append($left).append($right); - }); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - if (level + 1 < opts.depth) { - $tbody.append($linesRow); - } else { - $tbody.append($linesRow.hide()); - } - $childNodesRow = $(''); - $.each($childNodes, function() { - var $td = $('
").addClass("node-cell").attr("colspan", 2); var $childNodes = nodeData[opts.nodeChildren]; - if ($childNodes && $childNodes.length > 1) { - $nodeCell.attr("colspan", $childNodes.length * 2); - } - var $nodeDiv = createNode(nodeData, opts); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - - if ($childNodes && $childNodes.length > 0) { - // recurse until leaves found (-1) or to the level specified - var $childNodesRow; - // if (opts.depth == -1 || (level + 1 < opts.depth)) { - var $downLineRow = $("
").attr("colspan", $childNodes.length * 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $('
'); - $downLineCell.append($downLine); - if (level + 1 < opts.depth) { - $tbody.append($downLineRow); - } else { - $tbody.append($downLineRow.hide()); - } - - // draw the horizontal lines - var $linesRow = $('
  
'); - $td.attr("colspan", 2); - // recurse through children lists and items + var hasChildren = $childNodes ? $childNodes.length : false; + if (Object.keys(nodeData).length > 1) { // if nodeData has nested structure + $table = $(''); + $appendTo.append($table); + $.when(createNode(nodeData, opts)) + .done(function($nodeDiv) { + $table.append($nodeDiv.wrap('').closest('tr')); if (callback) { - buildNode(this, $td, level + 1, opts, callback); - } else { - buildNode(this, $td, level + 1, opts); - } - if (level + 1 < opts.depth) { - $childNodesRow.append($td); - } else { - $childNodesRow.append($td).hide(); + callback(); } + }) + .fail(function() { + console.log('Failed to creat node') }); - $tbody.append($childNodesRow); } - - $table.append($tbody); - $appendTo.append($table); - - // fire up callback every time of building up a node - if (callback) { - callback(); - } - - } - - // build the child nodes of specific node - function buildChildNode (nodeData, $appendTo, isChildNode, opts, callback) { - var $childNodes = nodeData.children || nodeData.siblings; - var $table, $tbody; - if (isChildNode) { - $table = $('
'); - $tbody = $(''); - - // create the node - var $nodeRow = $(''); - var $nodeCell = $('"); - var $downLineCell = $(""); - $.each($childNodes, function() { - var $left = $(''); - var $right = $(''); - $linesRow.append($left).append($right); - }); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - - if (isChildNode) { - $tbody.append($linesRow); - } else { - $appendTo.append($linesRow); + var isHidden = level + 1 >= opts.depth ? ' class="hidden"' : ''; + // draw the line close to parent node + $table.append(''); + // draw the lines close to children nodes + var linesRow = ''; + for (var i=1; i<$childNodes.length; i++) { + linesRow += ''; } - - var $childNodesRow = $(""); + linesRow += ''; + $table.append(linesRow); + // recurse through children nodes + var $childNodesRow = $(''); + $table.append($childNodesRow); $.each($childNodes, function() { - var $td = $("
'); - var $nodeDiv = createNode(nodeData, opts); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - } else { - $appendTo.children('tr:first').children('td:first') - .attr('colspan', $childNodes.length * 2); - } - - if ($childNodes && $childNodes.length > 0) { - // recurse until leaves found (-1) or to the level specified - var $downLineRow = $("
").attr("colspan", $childNodes.length * 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $('
'); - $downLineCell.append($downLine); - if (isChildNode) { - $tbody.append($downLineRow); - } else { - $appendTo.append($downLineRow); + // Construct the inferior nodes and connectiong lines + if (hasChildren) { + if (Object.keys(nodeData).length === 1) { // if nodeData is just an array + $table = $appendTo; } - - // Draw the horizontal lines - var $linesRow = $("
  
   
 
"); - $td.attr("colspan", 2); - // recurse through children lists and items - if (callback) { - buildChildNode(this, $td, true, opts, callback); - } else { - buildChildNode(this, $td, true, opts); - } + var $td = $(''); $childNodesRow.append($td); + buildHierarchy($td, this, level + 1, opts, callback); }); - - if (isChildNode) { - $tbody.append($childNodesRow); - } else { - $appendTo.append($childNodesRow); - } - } - - if (isChildNode) { - $table.append($tbody); - $appendTo.append($table); } - - // fire up callback every time of building up a node - if (callback) { - callback(); - } - } - // build the parent node of specific node - function buildParentNode(nodeData, $currentChart, opts) { + // build the child nodes of specific node + function buildChildNode ($appendTo, nodeData, opts, callback) { + var data = nodeData.children || nodeData.siblings; + $appendTo.find('td:first').attr('colspan', data.length * 2); + buildHierarchy($appendTo, { 'children': data }, 0, opts, callback); + } + // exposed method + function addChildren($node, data, opts) { var dtd = $.Deferred(); - var $table = $(""); - var $tbody = $(''); + var count = 0; + $.when(buildChildNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function() { + if (++count === data.children.length + 1) { + dtd.resolve(); + return dtd.promise(); + } + })) +   .done(function(){ + $node.children('.bottomEdge').data('childrenState', { 'exist': true, 'visible': true }); + if (isInAction($node)) { + switchUpDownArrow($node.children('.bottomEdge')); + } + }) +   .fail(function(){ + console.log('failed to add children nodes'); + }); + } - // Construct the node - var $nodeRow = $("").addClass("node-cells"); - var $nodeCell = $(""); - var $downLineCell = $(""); - var $left = $("").addClass("right top"); - var $right = $("").addClass("left top"); - $linesRow.append($left).append($right); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - $tbody.append($linesRow); - - $currentChart.closest('div.orgchart') - .prepend($table.append($tbody)).find('tbody:first') - .append($('').append($('
").addClass("node-cell").attr("colspan", 2); - var $nodeDiv = createNode(nodeData, opts ? opts : this.data('orgchart').options); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - - // recurse until leaves found (-1) or to the level specified - var $downLineRow = $("
").attr("colspan", 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $("
").addClass("down"); - $downLineCell.append($downLine); - $tbody.append($downLineRow); - - - // Draw the horizontal lines - var $linesRow = $("
  
') - .append($currentChart))); + // build the parent node of specific node + function buildParentNode(nodeData, opts, callback) { + var that = this; + var $table = $(''); + nodeData[(opts && opts.nodeRelationship) ? opts.nodeRelationship : 'relationship'] = '001'; + + $.when(createNode(nodeData, opts ? opts : this.data('orgchart').options)) + .done(function($nodeDiv) { + $table.append($nodeDiv.wrap('').closest('tr')); + $table.append(''); + var linesRow = ''; + $table.append('' + linesRow + ''); + var oc = that.children('.orgchart'); + oc.prepend($table) + .children('table:first') + .append('') + .children().children('tr:last').children() + .append(oc.children('table').last()); + callback(); + }) + .fail(function() { + console.log('Failed to create parent node'); + }); + } - dtd.resolve(); - return dtd.promise(); + // exposed method + function addParent($node, data, opts) { + buildParentNode.call($node.closest('.orgchart').parent(), data, opts, function(){ + $node.children('.title').after('') + .data('parentState', { 'exist': true, 'visible': true }); + if (isInAction($node)) { + switchUpDownArrow($node.children('.topEdge')); + } + }); } // subsequent processing of build sibling nodes - function subsequentProcess($target, siblingCount) { - $target.parent().prevAll('tr:gt(0)').children('td') - .attr('colspan', (siblingCount + 1) * 2) - .end().next().children('td').eq(0) - .after($('')); + function complementLine($oneSibling, siblingCount, existingSibligCount) { + var lines = ''; + for (var i = 0; i < existingSibligCount; i++) { + lines += ''; + } + $oneSibling.parent().prevAll('tr:gt(0)').children() + .attr('colspan', siblingCount * 2) + .end().next().children(':first') + .after($(lines)); } // build the sibling nodes of specific node - function buildSiblingNode(nodeData, $currentChart, opts) { + function buildSiblingNode($nodeChart, nodeData, opts) { var dtd = $.Deferred(); - var siblingCount = nodeData.siblings ? nodeData.siblings.length : nodeData.children.length; + var opts = opts || this.data('orgchart').options; + var newSiblingCount = nodeData.siblings ? nodeData.siblings.length : nodeData.children.length; + var existingSibligCount = $nodeChart.parent().is('td') ? $nodeChart.closest('tr').children().length : 1; + var siblingCount = existingSibligCount + newSiblingCount; var insertPostion = (siblingCount > 1) ? Math.floor(siblingCount/2 - 1) : 0; // just build the sibling nodes for the specific node - if ($currentChart.parent().is('td.node-container')) { - var $parent = $currentChart.closest('tr').prevAll('tr:last'); + if ($nodeChart.parent().is('td')) { + var $parent = $nodeChart.closest('tr').prevAll('tr:last'); if ($parent.is(':hidden')) { - $parent.show(); + $parent.removeClass('hidden'); } - $currentChart.closest('tr').prevAll('tr:lt(2)').remove(); + $nodeChart.closest('tr').prevAll('tr:lt(2)').remove(); var childCount = 0; - buildChildNode(nodeData, $currentChart.closest('tbody'), false, opts, function() { - if (++childCount === siblingCount + 1) { - subsequentProcess($currentChart.closest('tbody').children('tr:last').children('td') - .eq(insertPostion).after($currentChart.closest('td').unwrap()), siblingCount); + buildChildNode.call($nodeChart.closest('.orgchart').parent(),$nodeChart.parent().closest('table'), nodeData, opts, function() { + if (++childCount === newSiblingCount) { + if (existingSibligCount > 1) { + complementLine($nodeChart.parent().closest('table').children().children('tr:last').children('td:first') + .before($nodeChart.closest('td').siblings().andSelf().unwrap()), siblingCount, existingSibligCount); + } else { + complementLine($nodeChart.parent().closest('table').children().children('tr:last').children('td') + .eq(insertPostion).after($nodeChart.closest('td').unwrap()), siblingCount, 1); + } + dtd.resolve(); return dtd.promise(); } }); } else { // build the sibling nodes and parent node for the specific ndoe var nodeCount = 0; - buildNode(nodeData, $currentChart.closest('div.orgchart'), 0, opts, + buildHierarchy($nodeChart.closest('div.orgchart'), nodeData, 0, opts, function() { - if (++nodeCount === siblingCount + 1) { - subsequentProcess($currentChart.next().children('tbody:first').children('tr:last') - .children('td').eq(insertPostion).after($('
  
    ') - .append($currentChart)), siblingCount); + if (++nodeCount === siblingCount) { + complementLine($nodeChart.next().children().children('tr:last') + .children().eq(insertPostion).after($('') + .append($nodeChart)), siblingCount, 1); dtd.resolve(); return dtd.promise(); } @@ -941,4 +815,21 @@ } + function addSiblings($node, data, opts) { + $.when(buildSiblingNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts)) +   .done(function(){ + if (!$node.children('.leftEdge').length) { + $node.children('.topEdge').after('') + .siblings('.bottomEdge').after(''); + } + $node.children('.rightEdge, .leftEdge').data('siblingsState',{ 'exist': true, 'visible': true }); + if (isInAction($node)) { + collapseArrow($node); + } + }) +   .fail(function(){ + console.log('failed to adjust the position of org-chart!'); + }); + } + })(jQuery); diff --git a/jquery.orgchart.css b/jquery.orgchart.css index a65fba2f..cbbdefb8 100644 --- a/jquery.orgchart.css +++ b/jquery.orgchart.css @@ -18,7 +18,7 @@ */ .hidden { - display: none; + display: none!important; } .orgchart { diff --git a/jquery.orgchart.js b/jquery.orgchart.js index b8338f1d..b008e3d4 100644 --- a/jquery.orgchart.js +++ b/jquery.orgchart.js @@ -31,18 +31,18 @@ 'parentNodeSymbol': 'fa-users' }; - var opts = $.extend(defaultOptions, options); - this.data('orgchart', { 'options' : opts }); - switch (options) { - case 'buildNode': - return buildNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildChildNode': - return buildChildNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildParentNode': - return buildParentNode.apply(this, Array.prototype.splice.call(arguments, 1)); - case 'buildSiblingNode': - return buildSiblingNode.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'buildHierarchy': + return buildHierarchy.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addChildren': + return addChildren.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addParent': + return addParent.apply(this, Array.prototype.splice.call(arguments, 1)); + case 'addSiblings': + return addSiblings.apply(this, Array.prototype.splice.call(arguments, 1)); + default: // initiation time + var opts = $.extend(defaultOptions, options); + this.data('orgchart', { 'options' : opts }); } // build the org-chart @@ -57,7 +57,7 @@ } }); if ($.type(data) === 'object') { - buildNode(data, $chart, 0, opts); + buildHierarchy($chart, data, 0, opts); } else { $.ajax({ 'url': data, @@ -67,7 +67,7 @@ } }) .done(function(data, textStatus, jqXHR) { - buildNode(data, $chart, 0, opts); + buildHierarchy($chart, data, 0, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { console.log(errorThrown); @@ -109,40 +109,18 @@ return $chartContainer; }; - // determin whether the parent node of the specified node is visible on current chart view - function getParentState($node) { - if ($node.children('.spinner').length > 0) { - return {}; - } - var $parent = $node.closest('table').parent(); - if ($parent.is('td')) { - if ($parent.closest('tr').siblings().is(':visible')) { - return {"exist": true, "visible": true}; - } - return {"exist": true, "visible": false}; - } - return {"exist": false, "visible": false}; - } - function getChildrenState($node) { - if ($node.children('.spinner').length > 0) { - return {}; - } - var $children = $node.closest('tr').siblings(); - if ($children.length > 0) { - if ($children.is(':visible')) { - return {"exist": true, "visible": true}; - } - return {"exist": true, "visible": false}; - } - return {"exist": false, "visible": false}; - } - function getSiblingsState($node) { - if ($node.children('.spinner').length > 0) { - return {}; + // detect the exist/display state of related node + function getNodeState($node, relation) { + var $target = {}; + if (relation === 'parent') { + $target = $node.closest('table').parent().closest('tr').siblings(); + } else if (relation === 'children') { + $target = $node.closest('tr').siblings(); + } else { + $target = $node.closest('table').parent().siblings(); } - var $siblings = $node.closest('table').parent('td').siblings(); - if ($siblings.length > 0) { - if ($siblings.is(':visible')) { + if ($target.length) { + if ($target.is(':visible')) { return {"exist": true, "visible": true}; } return {"exist": true, "visible": false}; @@ -152,34 +130,33 @@ // recursively hide the ancestor node and sibling nodes of the specified node function hideAncestorsSiblings($node, dtd) { - var $nodeContainer = $node.closest('table').parent(); - if ($nodeContainer.parent().siblings('.node-cells').find('.spinner').length > 0) { + if ($node.closest('table').closest('tr').siblings(':first').find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } // firstly, hide the sibling nodes - if (getSiblingsState($node).visible) { + if (getNodeState($node, 'siblings').visible) { hideSiblings($node, false); } // hide the links - var $temp = $nodeContainer.parent().siblings(); + var $temp = $node.closest('table').closest('tr').siblings(); var $links = $temp.slice(1); $links.css('visibility', 'hidden'); // secondly, hide the superior nodes with animation var nodeOffset = $links.eq(0).outerHeight() + $links.eq(1).outerHeight(); var $parent = $temp.eq(0).find('div.node'); - var grandfatherVisible = getParentState($parent).visible; - if ($parent.length > 0 && $parent.is(':visible')) { + var grandfatherVisible = getNodeState($parent, 'parent').visible; + if ($parent.length && $parent.is(':visible')) { $parent.animate({'opacity': 0, 'top': +nodeOffset}, 300, function() { $parent.removeAttr('style'); $links.removeAttr('style'); - $temp.hide(); + $temp.addClass('hidden'); if ($parent.closest('table').parent().is('.orgchart') || !grandfatherVisible) { dtd.resolve(); } }); } // if the current node has the parent node, hide it recursively - if ($parent.length > 0 && grandfatherVisible) { + if ($parent.length && grandfatherVisible) { hideAncestorsSiblings($parent, dtd); } @@ -190,10 +167,10 @@ function showAncestorsSiblings($node) { var dtd = $.Deferred(); // just show only one superior level - var $temp = $node.closest('table').closest('tr').siblings().show(); + var $temp = $node.closest('table').closest('tr').siblings().removeClass('hidden'); // just show only one link - $temp.eq(2).children().slice(1, $temp.eq(2).children().length - 1).hide(); + $temp.eq(2).children().slice(1, $temp.eq(2).children().length - 1).addClass('hidden'); dtd.resolve(); // show the the only parent node with animation $temp.eq(0).find('div.node') @@ -208,7 +185,7 @@ // recursively hide the descendant nodes of the specified node function hideDescendants($node) { var dtd = $.Deferred(); - if ($node.closest('tr').siblings(':last').find('.spinner').length > 0) { + if ($node.closest('tr').siblings(':last').find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } var $links = $node.closest('tr').siblings(':lt(2)'); @@ -220,7 +197,7 @@ ) .done(function() { $links.removeAttr('style'); - $node.closest('tr').siblings().hide(); + $node.closest('tr').siblings().addClass('hidden'); dtd.resolve(); }); @@ -231,7 +208,7 @@ function showDescendants($node) { var dtd = $.Deferred(); // firstly, just show the only one inferior level of the child nodes - var $temp = $node.closest('tr').siblings().show(); + var $temp = $node.closest('tr').siblings().removeClass('hidden'); dtd.resolve(); // secondly, display the child nodes with animation var isLeaf = $temp.eq(2).children('td').length > 1 ? true : false; @@ -245,7 +222,7 @@ }); // lastly, remember to hide all the inferior nodes of child nodes of current node $children.each(function(index, child){ - $(child).closest('tr').siblings().hide(); + $(child).closest('tr').siblings().addClass('hidden'); }); return dtd.promise(); @@ -258,7 +235,7 @@ .animate({'opacity': 0, 'left': offset}, 300) ) .done(function() { - $siblings.hide(); + $siblings.addClass('hidden'); if (justSiblings) { $siblings.closest('.orgchart').css('opacity', 0);// hack for firefox } @@ -275,7 +252,7 @@ function hideSiblings($node, justSiblings) { var dtd = $.Deferred(); var $nodeContainer = $node.closest('table').parent(); - if ($nodeContainer.siblings().find('.spinner').length > 0) { + if ($nodeContainer.siblings().find('.spinner').length) { $node.closest('div.orgchart').data('inAjax', false); } var nodeOffset = $node.outerWidth(); @@ -283,7 +260,7 @@ var $upperLink = $nodeContainer.parent().prev().prev(); $upperLink.css('visibility', 'hidden') var $temp = $nodeContainer.parent().prev().children(); - $temp.slice(1, $temp.length -1).hide(); + $temp.slice(1, $temp.length -1).addClass('hidden'); $temp.eq(0).css('visibility', 'hidden'); $temp.eq($temp.length - 1).css('visibility', 'hidden'); // secondly, hide the sibling nodes with animation simultaneously @@ -297,18 +274,18 @@ function showSiblings($node) { var dtd = $.Deferred(); // firstly, show the sibling td tags - var $siblings = $node.closest('table').parent().siblings().show(); + var $siblings = $node.closest('table').parent().siblings().removeClass('hidden'); // secondly, show the links var $parent = $node.closest('table').closest('tr').siblings(); var $lowerLinks = $parent.eq(2).children(); - $lowerLinks.slice(1, $lowerLinks.length -1).show(); + $lowerLinks.slice(1, $lowerLinks.length -1).removeClass('hidden'); // thirdly, do some cleaning stuff if ($node.children('.topEdge').data('parentState').visible) { $siblings.each(function(index, sibling){ - $(sibling).find('div.node').closest('tr').siblings().hide(); + $(sibling).find('div.node').closest('tr').siblings().addClass('hidden'); }); } else { - $parent.show(); + $parent.removeClass('hidden'); } dtd.resolve(); // lastly, show the sibling nodes with animation @@ -324,7 +301,7 @@ return false; } - $arrow.hide(); + $arrow.addClass('hidden'); $node.append(''); $node.children().not('.spinner').css('opacity', 0.2); $chart.data('inAjax', true); @@ -335,7 +312,7 @@ // terminate loading status for requesting new nodes function endLoadingStatus($arrow, $node, options) { var $chart = $node.closest('div.orgchart'); - $arrow.show(); + $arrow.removeClass('hidden'); $node.find('.spinner').remove(); $node.children().removeAttr('style'); $chart.data('inAjax', false); @@ -374,6 +351,7 @@ // create node function createNode(nodeData, opts) { + var dtd = $.Deferred(); // construct the content of node var $nodeDiv = $('
', { 'id': nodeData[opts.nodeId] }) .addClass('node') @@ -404,7 +382,7 @@ var temp; if (event.type === 'mouseenter') { if ($topEdge.length) { - temp = getParentState($node); + temp = getNodeState($node, 'parent'); if (!$.isEmptyObject(temp)) { $topEdge.data('parentState', temp); } @@ -415,7 +393,7 @@ } } if ($bottomEdge.length) { - temp = getChildrenState($node); + temp = getNodeState($node, 'children'); if (!$.isEmptyObject(temp)) { $bottomEdge.data('childrenState', temp); } @@ -426,7 +404,7 @@ } } if ($leftEdge.length) { - temp = getSiblingsState($node); + temp = getNodeState($node, 'siblings'); if (!$.isEmptyObject(temp)) { $rightEdge.data('siblingsState', temp); $leftEdge.data('siblingsState', temp); @@ -451,11 +429,11 @@ }); // define click event handler for the top edge - $nodeDiv.children('.topEdge').on('click', function(event) { + $nodeDiv.on('click', '.topEdge', function(event) { var $that = $(this); var $node = $that.parent(); var parentState = $that.data('parentState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (parentState.exist) { @@ -468,7 +446,7 @@ $.when(hideAncestorsSiblings($node, dtd))    .done(function(){ parentState.visible = false; - if ($node.children('.leftEdge').length > 0) { + if ($node.children('.leftEdge').length) { $node.children('.leftEdge').data('siblingsState').visible = false; $node.children('.rightEdge').data('siblingsState').visible = false; } @@ -498,25 +476,18 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { + if ($node.closest('div.orgchart').data('inAjax')) { if (!$.isEmptyObject(data)) { - $.when(buildParentNode(data, $that.closest('table'), opts)) -   .done(function(){ - parentState.visible = true; - if (isInAction($node)) { - switchUpDownArrow($that); - } - }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + addParent($node, data, opts); } parentState.exist = true; } - // terminate the loading status - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get parent node data'); parentState.exist = true; + }) + .always(function() { // terminate the loading status endLoadingStatus($that, $node, opts); }); @@ -529,7 +500,7 @@ var $that = $(this); var $node = $that.parent(); var childrenState = $that.data('childrenState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (childrenState.exist) { @@ -564,31 +535,24 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { - if (data.children.length !== 0) { - var dtd = $.Deferred(); - var count = 0; - $.when(buildChildNode(data, $that.closest('tbody'), false, opts, function() { - if (++count === data.children.length + 1) { - dtd.resolve(); - return dtd.promise(); - } - })) -   .done(function(){ - childrenState.visible = true; - if (isInAction($node)) { - switchUpDownArrow($that); - } + if ($node.closest('div.orgchart').data('inAjax')) { + if (data.children.length) { + $.when(addChildren($node, data, opts)) + .done(function() { + childrenState.exist = true; + endLoadingStatus($that, $node, opts); }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + .fail(function() { + console.log('Failed to add children nodes'); + }); } - childrenState.exist = true; } - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get children nodes data'); childrenState.exist = true; + }) + .always(function() { endLoadingStatus($that, $node, opts); }); } @@ -596,11 +560,11 @@ }); // bind click event handler for the left and right edges - $nodeDiv.children('.leftEdge, .rightEdge').on('click', function(event) { + $nodeDiv.on('click', '.leftEdge, .rightEdge', function(event) { var $that = $(this); var $node = $that.parent(); var siblingsState = $that.data('siblingsState'); - if ($node.children('.spinner').length > 0) { + if ($node.children('.spinner').length) { return false; } if (siblingsState.exist) { @@ -640,16 +604,9 @@ "dataType": "json" }) .done(function(data, textStatus, jqXHR) { - if ($node.closest('div.orgchart').data('inAjax') === true) { + if ($node.closest('div.orgchart').data('inAjax')) { if (data.siblings || data.children) { - $.when(buildSiblingNode(data, $that.closest('table'), opts)) -   .done(function(){ - siblingsState.visible = true; - if (isInAction($node)) { - collapseArrow($node); - } - }) -   .fail(function(){ console.log('failed to adjust the position of org-chart!'); }); + addSiblings($node, data, opts); } $node.children('.topEdge').data('parentState').exist = true; siblingsState.exist = true; @@ -659,11 +616,12 @@ $that.siblings('.leftEdge').data('siblingsState', {'exist': true, 'visible': true}); } } - endLoadingStatus($that, $node, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { - console.log(errorThrown); + console.log('Failed to get sibling nodes data'); siblingsState.exist = true; + }) + .always(function() { endLoadingStatus($that, $node, opts); }); } @@ -673,7 +631,7 @@ $nodeDiv.children('.leftEdge').on('mouseenter mouseleave', function(event) { if (event.type === 'mouseenter') { var $rightEdge = $(this).siblings('.rightEdge'); - if (!getSiblingsState($(this)).visible) { + if (!getNodeState($(this), 'siblings').visible) { $rightEdge.addClass('rightEdgeMoveRight'); } else { $rightEdge.addClass('rightEdgeMoveLeft'); @@ -687,252 +645,168 @@ if (opts.createNode) { opts.createNode($nodeDiv, nodeData); } - - return $nodeDiv; + dtd.resolve($nodeDiv); + return dtd.promise(); } // recursively build the tree - function buildNode (nodeData, $appendTo, level, opts, callback) { - var $table = $(''); - var $tbody = $(''); - + function buildHierarchy ($appendTo, nodeData, level, opts, callback) { + var $table; // Construct the node - var $nodeRow = $("").addClass("node-cells"); - var $nodeCell = $(""); - var $downLineCell = $("'); - $.each($childNodes, function() { - var $left = $(''); - var $right = $(''); - $linesRow.append($left).append($right); - }); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - if (level + 1 < opts.depth) { - $tbody.append($linesRow); - } else { - $tbody.append($linesRow.hide()); - } - $childNodesRow = $(''); - $.each($childNodes, function() { - var $td = $('
").addClass("node-cell").attr("colspan", 2); var $childNodes = nodeData[opts.nodeChildren]; - if ($childNodes && $childNodes.length > 1) { - $nodeCell.attr("colspan", $childNodes.length * 2); - } - var $nodeDiv = createNode(nodeData, opts); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - - if ($childNodes && $childNodes.length > 0) { - // recurse until leaves found (-1) or to the level specified - var $childNodesRow; - // if (opts.depth == -1 || (level + 1 < opts.depth)) { - var $downLineRow = $("
").attr("colspan", $childNodes.length * 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $('
'); - $downLineCell.append($downLine); - if (level + 1 < opts.depth) { - $tbody.append($downLineRow); - } else { - $tbody.append($downLineRow.hide()); - } - - // draw the horizontal lines - var $linesRow = $('
  
'); - $td.attr("colspan", 2); - // recurse through children lists and items + var hasChildren = $childNodes ? $childNodes.length : false; + if (Object.keys(nodeData).length > 1) { // if nodeData has nested structure + $table = $(''); + $appendTo.append($table); + $.when(createNode(nodeData, opts)) + .done(function($nodeDiv) { + $table.append($nodeDiv.wrap('').closest('tr')); if (callback) { - buildNode(this, $td, level + 1, opts, callback); - } else { - buildNode(this, $td, level + 1, opts); - } - if (level + 1 < opts.depth) { - $childNodesRow.append($td); - } else { - $childNodesRow.append($td).hide(); + callback(); } + }) + .fail(function() { + console.log('Failed to creat node') }); - $tbody.append($childNodesRow); } - - $table.append($tbody); - $appendTo.append($table); - - // fire up callback every time of building up a node - if (callback) { - callback(); - } - - } - - // build the child nodes of specific node - function buildChildNode (nodeData, $appendTo, isChildNode, opts, callback) { - var $childNodes = nodeData.children || nodeData.siblings; - var $table, $tbody; - if (isChildNode) { - $table = $('
'); - $tbody = $(''); - - // create the node - var $nodeRow = $(''); - var $nodeCell = $('"); - var $downLineCell = $(""); - $.each($childNodes, function() { - var $left = $(''); - var $right = $(''); - $linesRow.append($left).append($right); - }); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - - if (isChildNode) { - $tbody.append($linesRow); - } else { - $appendTo.append($linesRow); + var isHidden = level + 1 >= opts.depth ? ' class="hidden"' : ''; + // draw the line close to parent node + $table.append(''); + // draw the lines close to children nodes + var linesRow = ''; + for (var i=1; i<$childNodes.length; i++) { + linesRow += ''; } - - var $childNodesRow = $(""); + linesRow += ''; + $table.append(linesRow); + // recurse through children nodes + var $childNodesRow = $(''); + $table.append($childNodesRow); $.each($childNodes, function() { - var $td = $("
'); - var $nodeDiv = createNode(nodeData, opts); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - } else { - $appendTo.children('tr:first').children('td:first') - .attr('colspan', $childNodes.length * 2); - } - - if ($childNodes && $childNodes.length > 0) { - // recurse until leaves found (-1) or to the level specified - var $downLineRow = $("
").attr("colspan", $childNodes.length * 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $('
'); - $downLineCell.append($downLine); - if (isChildNode) { - $tbody.append($downLineRow); - } else { - $appendTo.append($downLineRow); + // Construct the inferior nodes and connectiong lines + if (hasChildren) { + if (Object.keys(nodeData).length === 1) { // if nodeData is just an array + $table = $appendTo; } - - // Draw the horizontal lines - var $linesRow = $("
  
   
 
"); - $td.attr("colspan", 2); - // recurse through children lists and items - if (callback) { - buildChildNode(this, $td, true, opts, callback); - } else { - buildChildNode(this, $td, true, opts); - } + var $td = $(''); $childNodesRow.append($td); + buildHierarchy($td, this, level + 1, opts, callback); }); - - if (isChildNode) { - $tbody.append($childNodesRow); - } else { - $appendTo.append($childNodesRow); - } - } - - if (isChildNode) { - $table.append($tbody); - $appendTo.append($table); } - - // fire up callback every time of building up a node - if (callback) { - callback(); - } - } - // build the parent node of specific node - function buildParentNode(nodeData, $currentChart, opts) { + // build the child nodes of specific node + function buildChildNode ($appendTo, nodeData, opts, callback) { + var data = nodeData.children || nodeData.siblings; + $appendTo.find('td:first').attr('colspan', data.length * 2); + buildHierarchy($appendTo, { 'children': data }, 0, opts, callback); + } + // exposed method + function addChildren($node, data, opts) { var dtd = $.Deferred(); - var $table = $(""); - var $tbody = $(''); + var count = 0; + $.when(buildChildNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function() { + if (++count === data.children.length + 1) { + dtd.resolve(); + return dtd.promise(); + } + })) +   .done(function(){ + $node.children('.bottomEdge').data('childrenState', { 'exist': true, 'visible': true }); + if (isInAction($node)) { + switchUpDownArrow($node.children('.bottomEdge')); + } + }) +   .fail(function(){ + console.log('failed to add children nodes'); + }); + } - // Construct the node - var $nodeRow = $("").addClass("node-cells"); - var $nodeCell = $(""); - var $downLineCell = $(""); - var $left = $("").addClass("right top"); - var $right = $("").addClass("left top"); - $linesRow.append($left).append($right); - - // horizontal line shouldn't extend beyond the first and last child branches - $linesRow.find("td:first").removeClass("top").end().find("td:last").removeClass("top"); - $tbody.append($linesRow); - - $currentChart.closest('div.orgchart') - .prepend($table.append($tbody)).find('tbody:first') - .append($('').append($('
").addClass("node-cell").attr("colspan", 2); - var $nodeDiv = createNode(nodeData, opts ? opts : this.data('orgchart').options); - $nodeCell.append($nodeDiv); - $nodeRow.append($nodeCell); - $tbody.append($nodeRow); - - // recurse until leaves found (-1) or to the level specified - var $downLineRow = $("
").attr("colspan", 2); - $downLineRow.append($downLineCell); - - // draw the connecting line from the parent node to the horizontal line - var $downLine = $("
").addClass("down"); - $downLineCell.append($downLine); - $tbody.append($downLineRow); - - - // Draw the horizontal lines - var $linesRow = $("
  
') - .append($currentChart))); + // build the parent node of specific node + function buildParentNode(nodeData, opts, callback) { + var that = this; + var $table = $(''); + nodeData[(opts && opts.nodeRelationship) ? opts.nodeRelationship : 'relationship'] = '001'; + + $.when(createNode(nodeData, opts ? opts : this.data('orgchart').options)) + .done(function($nodeDiv) { + $table.append($nodeDiv.wrap('').closest('tr')); + $table.append(''); + var linesRow = ''; + $table.append('' + linesRow + ''); + var oc = that.children('.orgchart'); + oc.prepend($table) + .children('table:first') + .append('') + .children().children('tr:last').children() + .append(oc.children('table').last()); + callback(); + }) + .fail(function() { + console.log('Failed to create parent node'); + }); + } - dtd.resolve(); - return dtd.promise(); + // exposed method + function addParent($node, data, opts) { + buildParentNode.call($node.closest('.orgchart').parent(), data, opts, function(){ + $node.children('.title').after('') + .data('parentState', { 'exist': true, 'visible': true }); + if (isInAction($node)) { + switchUpDownArrow($node.children('.topEdge')); + } + }); } // subsequent processing of build sibling nodes - function subsequentProcess($target, siblingCount) { - $target.parent().prevAll('tr:gt(0)').children('td') - .attr('colspan', (siblingCount + 1) * 2) - .end().next().children('td').eq(0) - .after($('')); + function complementLine($oneSibling, siblingCount, existingSibligCount) { + var lines = ''; + for (var i = 0; i < existingSibligCount; i++) { + lines += ''; + } + $oneSibling.parent().prevAll('tr:gt(0)').children() + .attr('colspan', siblingCount * 2) + .end().next().children(':first') + .after($(lines)); } // build the sibling nodes of specific node - function buildSiblingNode(nodeData, $currentChart, opts) { + function buildSiblingNode($nodeChart, nodeData, opts) { var dtd = $.Deferred(); - var siblingCount = nodeData.siblings ? nodeData.siblings.length : nodeData.children.length; + var opts = opts || this.data('orgchart').options; + var newSiblingCount = nodeData.siblings ? nodeData.siblings.length : nodeData.children.length; + var existingSibligCount = $nodeChart.parent().is('td') ? $nodeChart.closest('tr').children().length : 1; + var siblingCount = existingSibligCount + newSiblingCount; var insertPostion = (siblingCount > 1) ? Math.floor(siblingCount/2 - 1) : 0; // just build the sibling nodes for the specific node - if ($currentChart.parent().is('td.node-container')) { - var $parent = $currentChart.closest('tr').prevAll('tr:last'); + if ($nodeChart.parent().is('td')) { + var $parent = $nodeChart.closest('tr').prevAll('tr:last'); if ($parent.is(':hidden')) { - $parent.show(); + $parent.removeClass('hidden'); } - $currentChart.closest('tr').prevAll('tr:lt(2)').remove(); + $nodeChart.closest('tr').prevAll('tr:lt(2)').remove(); var childCount = 0; - buildChildNode(nodeData, $currentChart.closest('tbody'), false, opts, function() { - if (++childCount === siblingCount + 1) { - subsequentProcess($currentChart.closest('tbody').children('tr:last').children('td') - .eq(insertPostion).after($currentChart.closest('td').unwrap()), siblingCount); + buildChildNode.call($nodeChart.closest('.orgchart').parent(),$nodeChart.parent().closest('table'), nodeData, opts, function() { + if (++childCount === newSiblingCount) { + if (existingSibligCount > 1) { + complementLine($nodeChart.parent().closest('table').children().children('tr:last').children('td:first') + .before($nodeChart.closest('td').siblings().andSelf().unwrap()), siblingCount, existingSibligCount); + } else { + complementLine($nodeChart.parent().closest('table').children().children('tr:last').children('td') + .eq(insertPostion).after($nodeChart.closest('td').unwrap()), siblingCount, 1); + } + dtd.resolve(); return dtd.promise(); } }); } else { // build the sibling nodes and parent node for the specific ndoe var nodeCount = 0; - buildNode(nodeData, $currentChart.closest('div.orgchart'), 0, opts, + buildHierarchy($nodeChart.closest('div.orgchart'), nodeData, 0, opts, function() { - if (++nodeCount === siblingCount + 1) { - subsequentProcess($currentChart.next().children('tbody:first').children('tr:last') - .children('td').eq(insertPostion).after($('
  
    ') - .append($currentChart)), siblingCount); + if (++nodeCount === siblingCount) { + complementLine($nodeChart.next().children().children('tr:last') + .children().eq(insertPostion).after($('') + .append($nodeChart)), siblingCount, 1); dtd.resolve(); return dtd.promise(); } @@ -941,4 +815,21 @@ } + function addSiblings($node, data, opts) { + $.when(buildSiblingNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts)) +   .done(function(){ + if (!$node.children('.leftEdge').length) { + $node.children('.topEdge').after('') + .siblings('.bottomEdge').after(''); + } + $node.children('.rightEdge, .leftEdge').data('siblingsState',{ 'exist': true, 'visible': true }); + if (isInAction($node)) { + collapseArrow($node); + } + }) +   .fail(function(){ + console.log('failed to adjust the position of org-chart!'); + }); + } + })(jQuery);