')
+ .addClass('node ' + (data.className || '') + (level > opts.visibleLevel ? ' slide-up' : ''));
+ if (opts.nodeTemplate) {
+ $nodeDiv.append(opts.nodeTemplate(data));
+ } else {
+ $nodeDiv.append('
' + data[opts.nodeTitle] + '
')
+ .append(typeof opts.nodeContent !== 'undefined' ? '
' + (data[opts.nodeContent] || '') + '
' : '');
+ }
+ //
+ var nodeData = $.extend({}, data);
+ delete nodeData.children;
+ $nodeDiv.data('nodeData', nodeData);
+ // append 4 direction arrows or expand/collapse buttons or reset buttons
+ var flags = data.relationship || '';
+ if ((opts.verticalLevel && level >= opts.verticalLevel) || data.vertical) {
+ if (Number(flags.substr(2, 1))) {
+ $nodeDiv.append(`
`)
+ .children('.title').prepend(`
`);
+ }
+ } else if (data.hybrid) {
+ if (Number(flags.substr(2, 1))) {
+ $nodeDiv.append(`
`)
+ .children('.title').prepend(`
`);
+ }
+ } else if (data.compact) {
+ $nodeDiv.css('grid-template-columns', `repeat(${Math.floor(Math.sqrt(data.children.length + 1))}, auto)`);
+ if (Number(flags.substr(2, 1))) {
+ $nodeDiv.append(`
+
+
+ `)
+ .children('.title').prepend(`
`);
+ }
+ } else if (data.associatedCompact) {
+
+ } else {
+ if (Number(flags.substr(0, 1))) {
+ $nodeDiv.append(`
`);
+ }
+ if (Number(flags.substr(1, 1))) {
+ $nodeDiv.append(`
`);
+ }
+ if (Number(flags.substr(2, 1))) {
+ $nodeDiv.append(`
`)
+ .children('.title').prepend(`
`);
+ }
+ }
+
+ $nodeDiv.on('mouseenter mouseleave', this.nodeEnterLeaveHandler.bind(this));
+ $nodeDiv.on('click', this.nodeClickHandler.bind(this));
+ $nodeDiv.on('click', '.topEdge', this.topEdgeClickHandler.bind(this));
+ $nodeDiv.on('click', '.bottomEdge', this.bottomEdgeClickHandler.bind(this));
+ $nodeDiv.on('click', '.leftEdge, .rightEdge', this.hEdgeClickHandler.bind(this));
+ $nodeDiv.on('click', '.toggleBtn', this.toggleVNodes.bind(this));
+ $nodeDiv.on('click', '> .backToCompactSymbol', this.backToCompactHandler.bind(this));
+ $nodeDiv.on('click', '> .backToLooseSymbol', this.backToLooseHandler.bind(this));
+
+ if (opts.draggable) {
+ this.bindDragDrop($nodeDiv);
+ this.touchHandled = false;
+ this.touchMoved = false;
+ this.touchTargetNode = null;
+ }
+ // allow user to append dom modification after finishing node create of orgchart
+ if (opts.createNode) {
+ opts.createNode($nodeDiv, data);
+ }
+
+ return $nodeDiv;
+ },
+ // recursively build the tree
+ buildHierarchy: function ($appendTo, data) {
+ var that = this;
+ var opts = this.options;
+ var level = 0;
+ var $nodeDiv;
+ if (data.level) {
+ level = data.level;
+ } else {
+ level = data.level = $appendTo.parentsUntil('.orgchart', '.nodes').length;
+ }
+ // Construct the node
+ if (Object.keys(data).length > 2) {
+ $nodeDiv = this.createNode(data);
+ $appendTo.append($nodeDiv);
+ }
+ // Construct the "inferior nodes"
+ if (data.children && data.children.length) {
+ var isHidden = level + 1 > opts.visibleLevel || (data.collapsed !== undefined && data.collapsed);
+ var $nodesLayer;
+ if ((opts.verticalLevel && (level + 1) >= opts.verticalLevel) || data.hybrid) {
+ $nodesLayer = $('
');
+ if (isHidden && (opts.verticalLevel && (level + 1) >= opts.verticalLevel)) {
+ $nodesLayer.addClass('hidden');
+ }
+ if (((opts.verticalLevel && level + 1 === opts.verticalLevel) || data.hybrid)
+ && !$appendTo.closest('.vertical').length) {
+ $appendTo.append($nodesLayer.addClass('vertical'));
+ } else {
+ $appendTo.append($nodesLayer);
+ }
+ } else if (data.compact) {
+ $nodeDiv.addClass('compact');
+ } else {
+ $nodesLayer = $('');
+ if (Object.keys(data).length === 2) {
+ $appendTo.append($nodesLayer);
+ } else {
+ if (isHidden) {
+ $appendTo.addClass('isChildrenCollapsed');
+ }
+ $appendTo.append($nodesLayer);
+ }
+ }
+ // recurse through children nodes
+ $.each(data.children, function () {
+ this.level = level + 1;
+ if (data.compact) {
+ that.buildHierarchy($nodeDiv, this);
+ } else {
+ var $nodeCell = $('- ');
+ $nodesLayer.append($nodeCell);
+ that.buildHierarchy($nodeCell, this);
+ }
+ });
+ }
+ },
+ // build the child nodes of specific node
+ buildChildNode: function ($appendTo, data) {
+ this.buildHierarchy($appendTo, { 'children': data });
+ },
+ // exposed method
+ addChildren: function ($node, data) {
+ this.buildChildNode($node.closest('.hierarchy'), data);
+ if (!$node.find('.parentNodeSymbol').length) {
+ $node.children('.title').prepend(``);
+ }
+ if ($node.closest('.nodes.vertical').length) {
+ if (!$node.children('.toggleBtn').length) {
+ $node.append(``);
+ }
+ } else {
+ if (!$node.children('.bottomEdge').length) {
+ $node.append(``);
+ }
+ }
+ if (this.isInAction($node)) {
+ this.switchVerticalArrow($node.children('.bottomEdge'));
+ }
+ },
+ // build the parent node of specific node
+ buildParentNode: function ($currentRoot, data) {
+ data.relationship = data.relationship || '001';
+ var $newRootWrapper = $('')
+ .find('.hierarchy').append(this.createNode(data)).end();
+ this.$chart.prepend($newRootWrapper)
+ .find('.hierarchy:first').append($currentRoot.closest('ul').addClass('nodes'));
+ },
+ // exposed method
+ addParent: function ($currentRoot, data) {
+ this.buildParentNode($currentRoot, data);
+ if (!$currentRoot.children('.topEdge').length) {
+ $currentRoot.children('.title').after(``);
+ }
+ if (this.isInAction($currentRoot)) {
+ this.switchVerticalArrow($currentRoot.children('.topEdge'));
+ }
+ },
+ // build the sibling nodes of specific node
+ buildSiblingNode: function ($nodeChart, data) {
+ var newSiblingCount = $.isArray(data) ? data.length : data.children.length;
+ var existingSibligCount = $nodeChart.parent().is('.nodes') ? $nodeChart.siblings().length + 1 : 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.closest('.nodes').parent().is('.hierarchy')) {
+ this.buildChildNode($nodeChart.parent().closest('.hierarchy'), data);
+ var $siblings = $nodeChart.parent().closest('.hierarchy').children('.nodes:last').children('.hierarchy');
+ if (existingSibligCount > 1) {
+ $siblings.eq(0).before($nodeChart.siblings().addBack().unwrap());
+ } else {
+ $siblings.eq(insertPostion).after($nodeChart.unwrap());
+ }
+ } else { // build the sibling nodes and parent node for the specific ndoe
+ this.buildHierarchy($nodeChart.parent().prepend($('
- ')).children('.hierarchy:first'), data);
+ $nodeChart.prevAll('.hierarchy').children('.nodes').children().eq(insertPostion).after($nodeChart);
+ }
+ },
+ //
+ addSiblings: function ($node, data) {
+ this.buildSiblingNode($node.closest('.hierarchy'), data);
+ $node.closest('.nodes').data('siblingsLoaded', true);
+ if (!$node.children('.leftEdge').length) {
+ $node.children('.topEdge').after(``);
+ }
+ if (this.isInAction($node)) {
+ this.switchHorizontalArrow($node);
+ $node.children('.topEdge').removeClass(this.options.icons.expandToUp).addClass(this.options.icons.collapseToDown);
+ }
+ },
+ // remove node and its descendent nodes
+ removeNodes: function ($node) {
+ var $wrapper = $node.closest('.hierarchy').parent();
+ if ($wrapper.parent().is('.hierarchy')) {
+ if (this.getNodeState($node, 'siblings').exist) {
+ $node.closest('.hierarchy').remove();
+ if ($wrapper.children().length === 1) {
+ $wrapper.find('.node:first .horizontalEdge').remove();
+ }
+ } else {
+ $wrapper.siblings('.node').find('.bottomEdge').remove()
+ .end().end().remove();
+ }
+ } else { // if $node is root node
+ $wrapper.closest('.orgchart').remove();
+ }
+ },
+ //
+ hideDropZones: function () {
+ // Remove all the 'this is a drop zone' indicators
+ var orgChartObj = this;
+ orgChartObj.$chart.find('.allowedDrop')
+ .removeClass('allowedDrop');
+ },
+ //
+ showDropZones: function (dragged) {
+ // Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later
+ // TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should!
+ var orgChartObj = this;
+ orgChartObj.$chart.find('.node')
+ .each(function (index, node) {
+ $(node).addClass('allowedDrop');
+ });
+ orgChartObj.$chart.data('dragged', $(dragged));
+ },
+ //
+ processExternalDrop: function (dropZone, dragged) {
+ // Allow an external drop event to be handled by one of our nodes
+ if (dragged) {
+ this.$chart.data('dragged', $(dragged));
+ }
+ var droppedOnNode = dropZone.closest('.node');
+ // would like to just call 'dropZoneHandler', but I can't reach it from here
+ // instead raise a drop event on the node element
+ droppedOnNode.triggerHandler({ 'type': 'drop' });
+ },
+ //
+ exportPDF: function (canvas, exportFilename) {
+ var doc = {};
+ var docWidth = Math.floor(canvas.width);
+ var docHeight = Math.floor(canvas.height);
+ if (!window.jsPDF) {
+ window.jsPDF = window.jspdf.jsPDF;
+ }
+
+ if (docWidth > docHeight) {
+ doc = new jsPDF({
+ orientation: 'landscape',
+ unit: 'px',
+ format: [docWidth, docHeight]
+ });
+ } else {
+ doc = new jsPDF({
+ orientation: 'portrait',
+ unit: 'px',
+ format: [docHeight, docWidth]
+ });
+ }
+ doc.addImage(canvas.toDataURL(), 'png', 0, 0);
+ doc.save(exportFilename + '.pdf');
+ },
+ //
+ exportPNG: function (canvas, exportFilename) {
+ var that = this;
+ var isWebkit = 'WebkitAppearance' in document.documentElement.style;
+ var isFf = !!window.sidebar;
+ var isEdge = navigator.appName === 'Microsoft Internet Explorer' || (navigator.appName === "Netscape" && navigator.appVersion.indexOf('Edge') > -1);
+ var $chartContainer = this.$chartContainer;
+
+ if ((!isWebkit && !isFf) || isEdge) {
+ window.navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png');
+ } else {
+ var selector = '.download-btn' + (that.options.chartClass !== '' ? '.' + that.options.chartClass : '');
+
+ if (!$chartContainer.find(selector).length) {
+ $chartContainer.append('');
+ }
+
+ $chartContainer.find(selector).attr('href', canvas.toDataURL())[0].click();
+ }
+ },
+ //
+ export: function (exportFilename, exportFileextension) {
+ var that = this;
+ exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;
+ exportFileextension = (typeof exportFileextension !== 'undefined') ? exportFileextension : this.options.exportFileextension;
+ if ($(this).children('.spinner').length) {
+ return false;
+ }
+ var $chartContainer = this.$chartContainer;
+ var $mask = $chartContainer.find('.mask');
+ if (!$mask.length) {
+ $chartContainer.append(`
`);
+ } else {
+ $mask.removeClass('hidden');
+ }
+ var sourceChart = $chartContainer.addClass('canvasContainer').find('.orgchart:not(".hidden")').get(0);
+ var flag = that.options.direction === 'l2r' || that.options.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:not(".hidden"):first').css('transform', '');
+ }
+ })
+ .then(function (canvas) {
+ $chartContainer.find('.mask').addClass('hidden');
+
+ if (exportFileextension.toLowerCase() === 'pdf') {
+ that.exportPDF(canvas, exportFilename);
+ } else {
+ that.exportPNG(canvas, exportFilename);
+ }
+
+ $chartContainer.removeClass('canvasContainer');
+ }, function () {
+ $chartContainer.removeClass('canvasContainer');
+ });
+ }
+ };
+
+ $.fn.orgchart = function (opts) {
+ return new OrgChart(this, opts).init();
+ };
+
+}));
diff --git a/templates/index.html b/templates/index.html
index ff8407ad0..2ac86d491 100755
--- a/templates/index.html
+++ b/templates/index.html
@@ -32,6 +32,7 @@
+