UNPKG

zoomla

Version:

16年专业研发|中文alexa排名第一的CMS品牌-基于dotNET core、功能强大,集成站群、微信开发、小程序与ERP及OA办公系统,支持国际语言和多民族语言,世界五百强与大型门户专用高端网站内核CMS系统

943 lines (901 loc) 39 kB
/* * jQuery OrgChart Plugin * https://github.com/dabeng/OrgChart * * Demos of jQuery OrgChart Plugin * http://dabeng.github.io/OrgChart/local-datasource/ * http://dabeng.github.io/OrgChart/ajax-datasource/ * http://dabeng.github.io/OrgChart/ondemand-loading-data/ * http://dabeng.github.io/OrgChart/option-createNode/ * http://dabeng.github.io/OrgChart/export-orgchart/ * http://dabeng.github.io/OrgChart/integrate-map/ * * Copyright 2016, dabeng * http://dabeng.github.io/ * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ 'use strict'; (function(factory) { if (typeof module === 'object' && typeof module.exports === 'object') { factory(require('jquery'), window, document); } else { factory(jQuery, window, document); } }(function($, window, document, undefined) { $.fn.orgchart = function(options) { var defaultOptions = { 'nodeTitle': 'name', 'nodeId': 'id', 'nodeChildren': 'children', 'toggleSiblingsResp': false, 'depth': 999, 'chartClass': '', 'exportButton': false, 'exportFilename': 'OrgChart', 'parentNodeSymbol': 'fa-users', 'draggable': false, 'direction': 't2b', 'pan': false, 'zoom': false }; switch (options) { 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)); case 'removeNodes': return removeNodes.apply(this, Array.prototype.splice.call(arguments, 1)); case 'getHierarchy': return getHierarchy.apply(this, Array.prototype.splice.call(arguments, 1)); default: // initiation time var opts = $.extend(defaultOptions, options); } // build the org-chart var $chartContainer = this; var data = opts.data; var $chart = $('<div>', { 'data': { 'options': opts }, 'class': 'orgchart' + (opts.chartClass !== '' ? ' ' + opts.chartClass : '') + (opts.direction !== 't2b' ? ' ' + opts.direction : ''), 'click': function(event) { if (!$(event.target).closest('.node').length) { $chart.find('.node.focused').removeClass('focused'); } } }); if ($.type(data) === 'object') { if (data instanceof $) { // ul datasource buildHierarchy($chart, buildJsonDS(data.children()), 0, opts); } else { // local json datasource buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts); } } else { $.ajax({ 'url': data, 'dataType': 'json', 'beforeSend': function () { $chart.append('<i class="fa fa-circle-o-notch fa-spin spinner"></i>'); } }) .done(function(data, textStatus, jqXHR) { buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts); }) .fail(function(jqXHR, textStatus, errorThrown) { console.log(errorThrown); }) .always(function() { $chart.children('.spinner').remove(); }); } $chartContainer.append($chart); // append the export button if (opts.exportButton && !$chartContainer.find('.oc-export-btn').length) { var $exportBtn = $('<button>', { 'class': 'oc-export-btn' + (opts.chartClass !== '' ? ' ' + opts.chartClass : ''), 'text': 'Export', 'click': function() { if ($(this).children('.spinner').length) { return false; } var $mask = $chartContainer.find('.mask'); if (!$mask.length) { $chartContainer.append('<div class="mask"><i class="fa fa-circle-o-notch fa-spin spinner"></i></div>'); } else { $mask.removeClass('hidden'); } var sourceChart = $chartContainer.addClass('canvasContainer').find('.orgchart:visible').get(0); var flag = opts.direction === 'l2r' || opts.direction === 'r2l'; html2canvas(sourceChart, { 'width': flag ? sourceChart.clientHeight : sourceChart.clientWidth, 'height': flag ? sourceChart.clientWidth : sourceChart.clientHeight, 'onclone': function(cloneDoc) { $(cloneDoc).find('.canvasContainer').css('overflow', 'visible') .find('.orgchart:visible:first').css('transform', ''); }, 'onrendered': function(canvas) { $chartContainer.find('.mask').addClass('hidden') .end().find('.oc-download-btn').attr('href', canvas.toDataURL())[0].click(); } }) .then(function() { $chartContainer.removeClass('canvasContainer'); }, function() { $chartContainer.removeClass('canvasContainer'); }); } }); var downloadBtn = '<a class="oc-download-btn' + (opts.chartClass !== '' ? ' ' + opts.chartClass : '') + '"' + ' download="' + opts.exportFilename + '.png"></a>'; $chartContainer.append($exportBtn).append(downloadBtn); } if (opts.pan) { $chartContainer.css('overflow', 'hidden'); $chart.on('mousedown',function(e){ var $this = $(this); if ($(e.target).closest('.node').length) { $this.data('panning', false); } else { $this.css('cursor', 'move').data('panning', true); } var lastX = 0; var lastY = 0; var lastTf = $this.css('transform'); if (lastTf !== 'none') { var temp = lastTf.split(','); if (lastTf.indexOf('3d') === -1) { lastX = parseInt(temp[4]); lastY = parseInt(temp[5]); } else { lastX = parseInt(temp[12]); lastY = parseInt(temp[13]); } } var startX = e.pageX - lastX; var startY = e.pageY - lastY; $(document).on('mousemove',function(ev) { var newX = ev.pageX - startX; var newY = ev.pageY - startY; var lastTf = $this.css('transform'); if (lastTf === 'none') { if (lastTf.indexOf('3d') === -1) { $this.css('transform', 'matrix(1, 0, 0, 1, ' + newX + ', ' + newY + ')'); } else { $this.css('transform', 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + newX + ', ' + newY + ', 0, 1)'); } } else { var matrix = lastTf.split(','); if (lastTf.indexOf('3d') === -1) { matrix[4] = ' ' + newX; matrix[5] = ' ' + newY + ')'; } else { matrix[12] = ' ' + newX; matrix[13] = ' ' + newY; } $this.css('transform', matrix.join(',')); } }); }); $(document).on('mouseup',function() { if ($chart.data('panning')) { $chart.css('cursor', 'default'); $(this).off('mousemove'); } }); } if (opts.zoom) { $chartContainer.on('wheel', function(event) { event.preventDefault(); var lastTf = $chart.css('transform'); var newScale = 1 + (event.originalEvent.deltaY > 0 ? -0.2 : 0.2); if (lastTf === 'none') { $chart.css('transform', 'scale(' + newScale + ',' + newScale + ')'); } else { if (lastTf.indexOf('3d') === -1) { $chart.css('transform', lastTf + ' scale(' + newScale + ',' + newScale + ')'); } else { $chart.css('transform', lastTf + ' scale3d(' + newScale + ',' + newScale + ', 1)'); } } }); } return $chartContainer; }; function buildJsonDS($li) { var subObj = { 'name': $li.contents().eq(0).text().trim(), 'relationship': ($li.parent().parent().is('li') ? '1': '0') + ($li.siblings('li').length ? 1: 0) + ($li.children('ul').length ? 1 : 0) }; if ($li[0].id) { subObj.id = $li[0].id; } $li.children('ul').children().each(function() { if (!subObj.children) { subObj.children = []; } subObj.children.push(buildJsonDS($(this))); }); return subObj; } function attachRel(data, flags) { data.relationship = flags + (data.children ? 1 : 0); if (data.children) { data.children.forEach(function(item) { attachRel(item, '1' + (data.children.length > 1 ? 1 :0)); }); } return data; } function loopChart($chart) { var $tr = $chart.find('tr:first'); var subObj = { 'id': $tr.find('.node')[0].id }; $tr.siblings(':last').children().each(function() { if (!subObj.children) { subObj.children = []; } subObj.children.push(loopChart($(this))); }); return subObj; } function getHierarchy($chart) { var $chart = $chart || $(this).find('.orgchart'); if (!$chart.find('.node:first')[0].id) { return 'Error: Nodes of orghcart to be exported must have id attribute!'; } return loopChart($chart); } // detect the exist/display state of related node function getNodeState($node, relation) { var $target = {}; if (relation === 'parent') { $target = $node.closest('table').closest('tr').siblings(':first').find('.node'); } else if (relation === 'children') { $target = $node.closest('tr').siblings(); } else { $target = $node.closest('table').parent().siblings(); } if ($target.length) { if ($target.is(':visible')) { return {"exist": true, "visible": true}; } return {"exist": true, "visible": false}; } return {"exist": false, "visible": false}; } // recursively hide the ancestor node and sibling nodes of the specified node function hideAncestorsSiblings($node) { var $temp = $node.closest('table').closest('tr').siblings(); if ($temp.eq(0).find('.spinner').length) { $node.closest('.orgchart').data('inAjax', false); } // hide the sibling nodes if (getNodeState($node, 'siblings').visible) { hideSiblings($node); } // hide the lines var $lines = $temp.slice(1); $lines.css('visibility', 'hidden'); // hide the superior nodes with transition var $parent = $temp.eq(0).find('.node'); var grandfatherVisible = getNodeState($parent, 'parent').visible; if ($parent.length && $parent.is(':visible')) { $parent.addClass('slide slide-down').one('transitionend', function() { $parent.removeClass('slide'); $lines.removeAttr('style'); $temp.addClass('hidden'); }); } // if the current node has the parent node, hide it recursively if ($parent.length && grandfatherVisible) { hideAncestorsSiblings($parent); } } // show the parent node of the specified node function showParent($node) { // just show only one superior level var $temp = $node.closest('table').closest('tr').siblings().removeClass('hidden'); // just show only one line $temp.eq(2).children().slice(1, -1).addClass('hidden'); // show parent node with animation var parent = $temp.eq(0).find('.node')[0]; repaint(parent); $(parent).addClass('slide').removeClass('slide-down').one('transitionend', function() { $(parent).removeClass('slide'); if (isInAction($node)) { switchVerticalArrow($node.children('.topEdge')); } }); } // recursively hide the descendant nodes of the specified node function hideDescendants($node) { var $temp = $node.closest('tr').siblings(); if ($temp.last().find('.spinner').length) { $node.closest('.orgchart').data('inAjax', false); } var $visibleNodes = $temp.last().find('.node:visible'); var $lines = $visibleNodes.closest('table').closest('tr').prevAll('.lines').css('visibility', 'hidden'); $visibleNodes.addClass('slide slide-up').eq(0).one('transitionend', function() { $visibleNodes.removeClass('slide'); $lines.removeAttr('style').addClass('hidden').siblings('.nodes').addClass('hidden'); if (isInAction($node)) { switchVerticalArrow($node.children('.bottomEdge')); } }); } // show the children nodes of the specified node function showDescendants($node) { var $descendants = $node.closest('tr').siblings().removeClass('hidden') .eq(2).children().find('tr:first').find('.node:visible'); // the two following statements are used to enforce browser to repaint repaint($descendants.get(0)); $descendants.addClass('slide').removeClass('slide-up').eq(0).one('transitionend', function() { $descendants.removeClass('slide'); if (isInAction($node)) { switchVerticalArrow($node.children('.bottomEdge')); } }); } // hide the sibling nodes of the specified node function hideSiblings($node, direction) { var $nodeContainer = $node.closest('table').parent(); if ($nodeContainer.siblings().find('.spinner').length) { $node.closest('.orgchart').data('inAjax', false); } if (direction) { if (direction === 'left') { $nodeContainer.prevAll().find('.node:visible').addClass('slide slide-right'); } else { $nodeContainer.nextAll().find('.node:visible').addClass('slide slide-left'); } } else { $nodeContainer.prevAll().find('.node:visible').addClass('slide slide-right'); $nodeContainer.nextAll().find('.node:visible').addClass('slide slide-left'); } var $animatedNodes = $nodeContainer.siblings().find('.slide'); var $lines = $animatedNodes.closest('.nodes').prevAll('.lines').css('visibility', 'hidden'); $animatedNodes.eq(0).one('transitionend', function() { $lines.removeAttr('style'); var $siblings = direction ? (direction === 'left' ? $nodeContainer.prevAll(':not(.hidden)') : $nodeContainer.nextAll(':not(.hidden)')) : $nodeContainer.siblings(); $nodeContainer.closest('.nodes').prev().children(':not(.hidden)') .slice(1, direction ? $siblings.length * 2 + 1 : -1).addClass('hidden'); $animatedNodes.removeClass('slide'); $siblings.find('.node:visible:gt(0)').removeClass('slide-left slide-right').addClass('slide-up') .end().find('.lines, .nodes').addClass('hidden') .end().addClass('hidden'); if (isInAction($node)) { switchHorizontalArrow($node); } }); } // show the sibling nodes of the specified node function showSiblings($node, direction) { // firstly, show the sibling td tags var $siblings = $(); if (direction) { if (direction === 'left') { $siblings = $node.closest('table').parent().prevAll().removeClass('hidden'); } else { $siblings = $node.closest('table').parent().nextAll().removeClass('hidden'); } } else { $siblings = $node.closest('table').parent().siblings().removeClass('hidden'); } // secondly, show the lines var $upperLevel = $node.closest('table').closest('tr').siblings(); if (direction) { $upperLevel.eq(2).children('.hidden').slice(0, $siblings.length * 2).removeClass('hidden'); } else { $upperLevel.eq(2).children('.hidden').removeClass('hidden'); } // thirdly, do some cleaning stuff if (!getNodeState($node, 'parent').visible) { $upperLevel.removeClass('hidden'); var parent = $upperLevel.find('.node')[0]; repaint(parent); $(parent).addClass('slide').removeClass('slide-down').one('transitionend', function() { $(this).removeClass('slide'); }); } // lastly, show the sibling nodes with animation $siblings.find('.node:visible').addClass('slide').removeClass('slide-left slide-right').eq(-1).one('transitionend', function() { $siblings.find('.node:visible').removeClass('slide'); if (isInAction($node)) { switchHorizontalArrow($node); $node.children('.topEdge').removeClass('fa-chevron-up').addClass('fa-chevron-down'); } }); } // start up loading status for requesting new nodes function startLoading($arrow, $node, options) { var $chart = $node.closest('.orgchart'); if (typeof $chart.data('inAjax') !== 'undefined' && $chart.data('inAjax') === true) { return false; } $arrow.addClass('hidden'); $node.append('<i class="fa fa-circle-o-notch fa-spin spinner"></i>'); $node.children().not('.spinner').css('opacity', 0.2); $chart.data('inAjax', true); $('.oc-export-btn' + (options.chartClass !== '' ? '.' + options.chartClass : '')).prop('disabled', true); return true; } // terminate loading status for requesting new nodes function endLoading($arrow, $node, options) { var $chart = $node.closest('div.orgchart'); $arrow.removeClass('hidden'); $node.find('.spinner').remove(); $node.children().removeAttr('style'); $chart.data('inAjax', false); $('.oc-export-btn' + (options.chartClass !== '' ? '.' + options.chartClass : '')).prop('disabled', false); } // whether the cursor is hovering over the node function isInAction($node) { return $node.children('.edge').attr('class').indexOf('fa-') > -1 ? true : false; } function switchVerticalArrow($arrow) { $arrow.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down'); } function switchHorizontalArrow($node) { var opts = $node.closest('.orgchart').data('options'); if (opts.toggleSiblingsResp && (typeof opts.ajaxURL === 'undefined' || $node.closest('.nodes').data('siblingsLoaded'))) { var $prevSib = $node.closest('table').parent().prev(); if ($prevSib.length) { if ($prevSib.is('.hidden')) { $node.children('.leftEdge').addClass('fa-chevron-left').removeClass('fa-chevron-right'); } else { $node.children('.leftEdge').addClass('fa-chevron-right').removeClass('fa-chevron-left'); } } var $nextSib = $node.closest('table').parent().next(); if ($nextSib.length) { if ($nextSib.is('.hidden')) { $node.children('.rightEdge').addClass('fa-chevron-right').removeClass('fa-chevron-left'); } else { $node.children('.rightEdge').addClass('fa-chevron-left').removeClass('fa-chevron-right'); } } } else { var $sibs = $node.closest('table').parent().siblings(); var sibsVisible = $sibs.length ? !$sibs.is('.hidden') : false; $node.children('.leftEdge').toggleClass('fa-chevron-right', sibsVisible).toggleClass('fa-chevron-left', !sibsVisible); $node.children('.rightEdge').toggleClass('fa-chevron-left', sibsVisible).toggleClass('fa-chevron-right', !sibsVisible); } } function repaint(node) { node.style.offsetWidth = node.offsetWidth; } // create node function createNode(nodeData, level, opts) { var dtd = $.Deferred(); // construct the content of node var $nodeDiv = $('<div' + (opts.draggable ? ' draggable="true"' : '') + (nodeData[opts.nodeId] ? ' id="' + nodeData[opts.nodeId] + '"' : '') + '>') .addClass('node ' + (nodeData.className || '') + (level >= opts.depth ? ' slide-up' : '')) .append('<div class="title">' + nodeData[opts.nodeTitle] + '</div>') .append(typeof opts.nodeContent !== 'undefined' ? '<div class="content">' + (nodeData[opts.nodeContent] || '') + '</div>' : ''); // append 4 direction arrows var flags = nodeData.relationship || ''; if (Number(flags.substr(0,1))) { $nodeDiv.append('<i class="edge verticalEdge topEdge fa"></i>'); } if(Number(flags.substr(1,1))) { $nodeDiv.append('<i class="edge horizontalEdge rightEdge fa"></i>' + '<i class="edge horizontalEdge leftEdge fa"></i>'); } if(Number(flags.substr(2,1))) { $nodeDiv.append('<i class="edge verticalEdge bottomEdge fa"></i>') .children('.title').prepend('<i class="fa '+ opts.parentNodeSymbol + ' symbol"></i>'); } $nodeDiv.on('mouseenter mouseleave', function(event) { var $node = $(this), flag = false; var $topEdge = $node.children('.topEdge'); var $rightEdge = $node.children('.rightEdge'); var $bottomEdge = $node.children('.bottomEdge'); var $leftEdge = $node.children('.leftEdge'); if (event.type === 'mouseenter') { if ($topEdge.length) { flag = getNodeState($node, 'parent').visible; $topEdge.toggleClass('fa-chevron-up', !flag).toggleClass('fa-chevron-down', flag); } if ($bottomEdge.length) { flag = getNodeState($node, 'children').visible; $bottomEdge.toggleClass('fa-chevron-down', !flag).toggleClass('fa-chevron-up', flag); } if ($leftEdge.length) { switchHorizontalArrow($node); } } else { $node.children('.edge').removeClass('fa-chevron-up fa-chevron-down fa-chevron-right fa-chevron-left'); } }); // define click event handler $nodeDiv.on('click', function(event) { $(this).closest('.orgchart').find('.focused').removeClass('focused'); $(this).addClass('focused'); }); // define click event handler for the top edge $nodeDiv.on('click', '.topEdge', function(event) { var $that = $(this); var $node = $that.parent(); var parentState = getNodeState($node, 'parent'); if (parentState.exist) { var $parent = $node.closest('table').closest('tr').siblings(':first').find('.node'); if ($parent.is('.slide')) { } // hide the ancestor nodes and sibling nodes of the specified node if (parentState.visible) { hideAncestorsSiblings($node); $parent.one('transitionend', function() { if (isInAction($node)) { switchVerticalArrow($that); switchHorizontalArrow($node); } }); } else { // show the ancestors and siblings showParent($node); } } else { // load the new parent node of the specified node by ajax request var nodeId = $that.parent()[0].id; // start up loading status if (startLoading($that, $node, opts)) { // load new nodes $.ajax({ 'url': opts.ajaxURL.parent + nodeId + '/', 'dataType': 'json' }) .done(function(data) { if ($node.closest('.orgchart').data('inAjax')) { if (!$.isEmptyObject(data)) { addParent.call($node.closest('.orgchart').parent(), $node, data, opts); } } }) .fail(function() { console.log('Failed to get parent node data'); }) .always(function() { endLoading($that, $node, opts); }); } } }); // bind click event handler for the bottom edge $nodeDiv.on('click', '.bottomEdge', function(event) { var $that = $(this); var $node = $that.parent(); var childrenState = getNodeState($node, 'children'); if (childrenState.exist) { var $children = $node.closest('tr').siblings(':last'); if ($children.find('.node:visible').is('.slide')) { } // hide the descendant nodes of the specified node if (childrenState.visible) { hideDescendants($node); } else { // show the descendants showDescendants($node); } } else { // load the new children nodes of the specified node by ajax request var nodeId = $that.parent()[0].id; if (startLoading($that, $node, opts)) { $.ajax({ 'url': opts.ajaxURL.children + nodeId + '/', 'dataType': 'json' }) .done(function(data, textStatus, jqXHR) { if ($node.closest('.orgchart').data('inAjax')) { if (data.children.length) { addChildren($node, data, $.extend({}, opts, { depth: 0 })); } } }) .fail(function(jqXHR, textStatus, errorThrown) { console.log('Failed to get children nodes data'); }) .always(function() { endLoading($that, $node, opts); }); } } }); // bind click event handler for the left and right edges $nodeDiv.on('click', '.leftEdge, .rightEdge', function(event) { var $that = $(this); var $node = $that.parent(); var siblingsState = getNodeState($node, 'siblings'); if (siblingsState.exist) { var $siblings = $node.closest('table').parent().siblings(); if ($siblings.find('.node:visible').is('.slide')) { } if (opts.toggleSiblingsResp) { var $prevSib = $node.closest('table').parent().prev(); var $nextSib = $node.closest('table').parent().next(); if ($that.is('.leftEdge')) { if ($prevSib.is('.hidden')) { showSiblings($node, 'left'); } else { hideSiblings($node, 'left'); } } else { if ($nextSib.is('.hidden')) { showSiblings($node, 'right'); } else { hideSiblings($node, 'right'); } } } else { if (siblingsState.visible) { hideSiblings($node); } else { showSiblings($node); } } } else { // load the new sibling nodes of the specified node by ajax request var nodeId = $that.parent()[0].id; var url = (getNodeState($node, 'parent').exist) ? opts.ajaxURL.siblings : opts.ajaxURL.families; if (startLoading($that, $node, opts)) { $.ajax({ 'url': url + nodeId + '/', 'dataType': 'json' }) .done(function(data, textStatus, jqXHR) { if ($node.closest('.orgchart').data('inAjax')) { if (data.siblings || data.children) { addSiblings($node, data, opts); } } }) .fail(function(jqXHR, textStatus, errorThrown) { console.log('Failed to get sibling nodes data'); }) .always(function() { endLoading($that, $node, opts); }); } } }); if (opts.draggable) { $nodeDiv.on('dragstart', function(event) { event.originalEvent.dataTransfer.setData('text/html', 'hack for firefox'); var $dragged = $(this); var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); var $dragHier = $dragged.closest('table').find('.node'); $dragged.closest('.orgchart') .data('dragged', $dragged) .find('.node').each(function(index, node) { if ($dragHier.index(node) === -1) { if (opts.dropCriteria) { if (opts.dropCriteria($dragged, $dragZone, $(node))) { $(node).addClass('allowedDrop'); } } else { $(node).addClass('allowedDrop'); } } }); }) .on('dragover', function(event) { event.preventDefault(); var $dropZone = $(this); var $dragged = $dropZone.closest('.orgchart').data('dragged'); var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); if ($dragged.closest('table').find('.node').index($dropZone) > -1 || (opts.dropCriteria && !opts.dropCriteria($dragged, $dragZone, $dropZone))) { event.originalEvent.dataTransfer.dropEffect = 'none'; } }) .on('dragend', function(event) { $(this).closest('.orgchart').find('.allowedDrop').removeClass('allowedDrop'); }) .on('drop', function(event) { var $dropZone = $(this); var $orgchart = $dropZone.closest('.orgchart'); var $dragged = $orgchart.data('dragged'); $orgchart.find('.allowedDrop').removeClass('allowedDrop'); var $dragZone = $dragged.closest('.nodes').siblings().eq(0).children(); // firstly, deal with the hierarchy of drop zone if (!$dropZone.closest('tr').siblings().length) { // if the drop zone is a leaf node $dropZone.append('<i class="edge verticalEdge bottomEdge fa"></i>') .parent().attr('colspan', 2) .parent().after('<tr class="lines"><td colspan="2"><div class="down"></div></td></tr>' + '<tr class="lines"><td class="right">&nbsp;</td><td class="left">&nbsp;</td></tr>' + '<tr class="nodes"></tr>') .siblings(':last').append($dragged.find('.horizontalEdge').remove().end().closest('table').parent()); } else { var dropColspan = parseInt($dropZone.parent().attr('colspan')) + 2; var horizontalEdges = '<i class="edge horizontalEdge rightEdge fa"></i><i class="edge horizontalEdge leftEdge fa"></i>'; $dropZone.closest('tr').next().addBack().children().attr('colspan', dropColspan); if (!$dragged.find('.horizontalEdge').length) { $dragged.append(horizontalEdges); } $dropZone.closest('tr').siblings().eq(1).children(':last').before('<td class="left top">&nbsp;</td><td class="right top">&nbsp;</td>') .end().next().append($dragged.closest('table').parent()); var $dropSibs = $dragged.closest('table').parent().siblings().find('.node:first'); if ($dropSibs.length === 1) { $dropSibs.append(horizontalEdges); } } // secondly, deal with the hierarchy of dragged node var dragColspan = parseInt($dragZone.attr('colspan')); if (dragColspan > 2) { $dragZone.attr('colspan', dragColspan - 2) .parent().next().children().attr('colspan', dragColspan - 2) .end().next().children().slice(1, 3).remove(); var $dragSibs = $dragZone.parent().siblings('.nodes').children().find('.node:first'); if ($dragSibs.length ===1) { $dragSibs.find('.horizontalEdge').remove(); } } else { $dragZone.removeAttr('colspan') .find('.bottomEdge').remove() .end().end().siblings().remove(); } $orgchart.triggerHandler({ 'type': 'nodedropped.orgchart', 'draggedNode': $dragged, 'dragZone': $dragZone.children(), 'dropZone': $dropZone }); }); } // allow user to append dom modification after finishing node create of orgchart if (opts.createNode) { opts.createNode($nodeDiv, nodeData); } dtd.resolve($nodeDiv); return dtd.promise(); } // recursively build the tree function buildHierarchy ($appendTo, nodeData, level, opts, callback) { var $table; // Construct the node var $childNodes = nodeData[opts.nodeChildren]; var hasChildren = $childNodes ? $childNodes.length : false; if (Object.keys(nodeData).length > 1) { // if nodeData has nested structure $table = $('<table>'); $appendTo.append($table); $.when(createNode(nodeData, level, opts)) .done(function($nodeDiv) { $table.append($nodeDiv.wrap('<tr><td' + (hasChildren ? ' colspan="' + $childNodes.length * 2 + '"' : '') + '></td></tr>').closest('tr')); if (callback) { callback(); } }) .fail(function() { console.log('Failed to creat node') }); } // Construct the inferior nodes and connectiong lines if (hasChildren) { if (Object.keys(nodeData).length === 1) { // if nodeData is just an array $table = $appendTo; } var isHidden = level + 1 >= opts.depth ? ' hidden' : ''; // draw the line close to parent node $table.append('<tr class="lines' + isHidden + '"><td colspan="' + $childNodes.length * 2 + '"><div class="down"></div></td></tr>'); // draw the lines close to children nodes var linesRow = '<tr class="lines' + isHidden + '"><td class="right">&nbsp;</td>'; for (var i=1; i<$childNodes.length; i++) { linesRow += '<td class="left top">&nbsp;</td><td class="right top">&nbsp;</td>'; } linesRow += '<td class="left">&nbsp;</td></tr>'; $table.append(linesRow); // recurse through children nodes var $childNodesRow = $('<tr class="nodes' + isHidden + '">'); $table.append($childNodesRow); $.each($childNodes, function() { var $td = $('<td colspan="2">'); $childNodesRow.append($td); buildHierarchy($td, this, level + 1, opts, callback); }); } } // build the child nodes of specific node function buildChildNode ($appendTo, nodeData, opts, callback) { var opts = opts || $appendTo.closest('.orgchart').data('options'); 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 count = 0; buildChildNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function() { if (++count === data.children.length) { if (!$node.children('.bottomEdge').length) { $node.append('<i class="edge verticalEdge bottomEdge fa"></i>'); } if (!$node.find('.symbol').length) { $node.children('.title').prepend('<i class="fa '+ opts.parentNodeSymbol + ' symbol"></i>'); } showDescendants($node); } }); } // build the parent node of specific node function buildParentNode($currentRoot, nodeData, opts, callback) { var that = this; var $table = $('<table>'); nodeData.relationship = '001'; $.when(createNode(nodeData, 0, opts || $currentRoot.closest('.orgchart').data('options'))) .done(function($nodeDiv) { $table.append($nodeDiv.removeClass('slide-up').addClass('slide-down').wrap('<tr class="hidden"><td colspan="2"></td></tr>').closest('tr')); $table.append('<tr class="lines hidden"><td colspan="2"><div class="down"></div></td></tr>'); var linesRow = '<td class="right">&nbsp;</td><td class="left">&nbsp;</td>'; $table.append('<tr class="lines hidden">' + linesRow + '</tr>'); var oc = that.children('.orgchart'); oc.prepend($table) .children('table:first').append('<tr class="nodes"><td colspan="2"></td></tr>') .children().children('tr:last').children().append(oc.children('table').last()); callback(); }) .fail(function() { console.log('Failed to create parent node'); }); } // exposed method function addParent($currentRoot, data, opts) { buildParentNode.call(this, $currentRoot, data, opts, function() { if (!$currentRoot.children('.topEdge').length) { $currentRoot.children('.title').after('<i class="edge verticalEdge topEdge fa"></i>'); } showParent($currentRoot); }); } // subsequent processing of build sibling nodes function complementLine($oneSibling, siblingCount, existingSibligCount) { var lines = ''; for (var i = 0; i < existingSibligCount; i++) { lines += '<td class="left top">&nbsp;</td><td class="right top">&nbsp;</td>'; } $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($nodeChart, nodeData, opts, callback) { var opts = opts || $nodeChart.closest('.orgchart').data('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 ($nodeChart.parent().is('td')) { var $parent = $nodeChart.closest('tr').prevAll('tr:last'); $nodeChart.closest('tr').prevAll('tr:lt(2)').remove(); var childCount = 0; buildChildNode.call($nodeChart.closest('.orgchart').parent(),$nodeChart.parent().closest('table'), nodeData, opts, function() { if (++childCount === newSiblingCount) { var $siblingTds = $nodeChart.parent().closest('table').children().children('tr:last').children('td'); if (existingSibligCount > 1) { complementLine($siblingTds.eq(0).before($nodeChart.closest('td').siblings().andSelf().unwrap()), siblingCount, existingSibligCount); $siblingTds.addClass('hidden').find('.node').addClass('slide-left'); } else { complementLine($siblingTds.eq(insertPostion).after($nodeChart.closest('td').unwrap()), siblingCount, 1); $siblingTds.not(':eq(' + insertPostion + 1 + ')').addClass('hidden') .slice(0, insertPostion).find('.node').addClass('slide-right') .end().end().slice(insertPostion).find('.node').addClass('slide-left'); } callback(); } }); } else { // build the sibling nodes and parent node for the specific ndoe var nodeCount = 0; buildHierarchy($nodeChart.closest('.orgchart'), nodeData, 0, opts, function() { if (++nodeCount === siblingCount) { complementLine($nodeChart.next().children().children('tr:last') .children().eq(insertPostion).after($('<td colspan="2">') .append($nodeChart)), siblingCount, 1); $nodeChart.closest('tr').siblings().eq(0).addClass('hidden').find('.node').addClass('slide-down'); $nodeChart.parent().siblings().addClass('hidden') .slice(0, insertPostion).find('.node').addClass('slide-right') .end().end().slice(insertPostion).find('.node').addClass('slide-left'); callback(); } }); } } function addSiblings($node, data, opts) { buildSiblingNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function() { $node.closest('.nodes').data('siblingsLoaded', true); if (!$node.children('.leftEdge').length) { $node.children('.topEdge').after('<i class="edge horizontalEdge rightEdge fa"></i><i class="edge horizontalEdge leftEdge fa"></i>'); } showSiblings($node); }); } function removeNodes($node) { var $parent = $node.closest('table').parent(); var $sibs = $parent.parent().siblings(); if ($parent.is('td')) { if (getNodeState($node, 'siblings').exist) { $sibs.eq(2).children('.top:lt(2)').remove(); $sibs.eq(':lt(2)').children().attr('colspan', $sibs.eq(2).children().length); $parent.remove(); } else { $sibs.eq(0).children().removeAttr('colspan') .find('.bottomEdge').remove() .end().end().siblings().remove(); } } else { $parent.add($parent.siblings()).remove(); } } }));