')\n .addClass('node ' + (data.className || '') + (level > opts.visibleLevel ? ' slide-up' : ''));\n if (opts.nodeTemplate) {\n $nodeDiv.append(opts.nodeTemplate(data));\n } else {\n $nodeDiv.append('
' + data[opts.nodeTitle] + '
')\n .append(typeof opts.nodeContent !== 'undefined' ? '
' + (data[opts.nodeContent] || '') + '
' : '');\n }\n //\n var nodeData = $.extend({}, data);\n delete nodeData.children;\n $nodeDiv.data('nodeData', nodeData);\n // append 4 direction arrows or expand/collapse buttons\n var flags = data.relationship || '';\n if (opts.verticalLevel && level >= opts.verticalLevel) {\n if ((level + 1) > opts.verticalLevel && Number(flags.substr(2,1))) {\n $nodeDiv.append('
')\n .children('.title').prepend('
');\n }\n } else {\n if (Number(flags.substr(0,1))) {\n $nodeDiv.append('
');\n }\n if(Number(flags.substr(1,1))) {\n $nodeDiv.append('
' +\n '
');\n }\n if(Number(flags.substr(2,1))) {\n $nodeDiv.append('
')\n .children('.title').prepend('
');\n }\n }\n\n $nodeDiv.on('mouseenter mouseleave', this.nodeEnterLeaveHandler.bind(this));\n $nodeDiv.on('click', this.nodeClickHandler.bind(this));\n $nodeDiv.on('click', '.topEdge', this.topEdgeClickHandler.bind(this));\n $nodeDiv.on('click', '.bottomEdge', this.bottomEdgeClickHandler.bind(this));\n $nodeDiv.on('click', '.leftEdge, .rightEdge', this.hEdgeClickHandler.bind(this));\n $nodeDiv.on('click', '.toggleBtn', this.toggleVNodes.bind(this));\n\n if (opts.draggable) {\n this.bindDragDrop($nodeDiv);\n this.touchHandled = false;\n this.touchMoved = false;\n this.touchTargetNode = null;\n }\n // allow user to append dom modification after finishing node create of orgchart\n if (opts.createNode) {\n opts.createNode($nodeDiv, data);\n }\n\n return $nodeDiv;\n },\n // recursively build the tree\n buildHierarchy: function ($appendTo, data) {\n var that = this;\n var opts = this.options;\n var level = 0;\n if (data.level) {\n level = data.level;\n } else {\n level = data.level = $appendTo.parentsUntil('.orgchart', '.nodes').length;\n }\n // Construct the node\n if (Object.keys(data).length > 2) {\n var $nodeDiv = this.createNode(data);\n if (opts.verticalLevel && level >= opts.verticalLevel) {\n $appendTo.append($nodeDiv);\n } else {\n $appendTo.append($nodeDiv);\n }\n }\n // Construct the \"inferior nodes\"\n if (data.children && data.children.length) {\n var isHidden = level + 1 > opts.visibleLevel || (data.collapsed !== undefined && data.collapsed);\n var isVerticalLayer = opts.verticalLevel && (level + 1) >= opts.verticalLevel;\n var $nodesLayer;\n if (isVerticalLayer) {\n $nodesLayer = $('
');\n if (isHidden && level + 1 >= opts.verticalLevel) {\n $nodesLayer.addClass('hidden');\n }\n if (level + 1 === opts.verticalLevel) {\n $appendTo.addClass('hybrid').append($nodesLayer.addClass('vertical'));\n } else {\n $appendTo.append($nodesLayer);\n }\n } else {\n $nodesLayer = $('');\n if (Object.keys(data).length === 2) {\n $appendTo.append($nodesLayer);\n } else {\n if (isHidden) {\n $appendTo.addClass('isChildrenCollapsed');\n }\n $appendTo.append($nodesLayer);\n }\n }\n // recurse through children nodes\n $.each(data.children, function () {\n var $nodeCell = $('- ');\n $nodesLayer.append($nodeCell);\n this.level = level + 1;\n that.buildHierarchy($nodeCell, this);\n });\n }\n },\n // build the child nodes of specific node\n buildChildNode: function ($appendTo, data) {\n this.buildHierarchy($appendTo, { 'children': data });\n },\n // exposed method\n addChildren: function ($node, data) {\n this.buildChildNode($node.closest('.hierarchy'), data);\n if (!$node.find('.symbol').length) {\n $node.children('.title').prepend('');\n }\n if ($node.closest('.nodes.vertical').length) {\n if (!$node.children('.toggleBtn').length) {\n $node.append('');\n }\n } else {\n if (!$node.children('.bottomEdge').length) {\n $node.append('');\n }\n }\n if (this.isInAction($node)) {\n this.switchVerticalArrow($node.children('.bottomEdge'));\n }\n },\n // build the parent node of specific node\n buildParentNode: function ($currentRoot, data) {\n data.relationship = data.relationship || '001';\n var $newRootWrapper = $('')\n .find('.hierarchy').append(this.createNode(data)).end();\n this.$chart.prepend($newRootWrapper)\n .find('.hierarchy:first').append($currentRoot.closest('ul').addClass('nodes'));\n },\n // exposed method\n addParent: function ($currentRoot, data) {\n this.buildParentNode($currentRoot, data);\n if (!$currentRoot.children('.topEdge').length) {\n $currentRoot.children('.title').after('');\n }\n if (this.isInAction($currentRoot)) {\n this.switchVerticalArrow($currentRoot.children('.topEdge'));\n }\n },\n // build the sibling nodes of specific node\n buildSiblingNode: function ($nodeChart, data) {\n var newSiblingCount = $.isArray(data) ? data.length : data.children.length;\n var existingSibligCount = $nodeChart.parent().is('.nodes') ? $nodeChart.siblings().length + 1 : 1;\n var siblingCount = existingSibligCount + newSiblingCount;\n var insertPostion = (siblingCount > 1) ? Math.floor(siblingCount/2 - 1) : 0;\n // just build the sibling nodes for the specific node\n if ($nodeChart.closest('.nodes').parent().is('.hierarchy')) {\n this.buildChildNode($nodeChart.parent().closest('.hierarchy'), data);\n var $siblings = $nodeChart.parent().closest('.hierarchy').children('.nodes:last').children('.hierarchy');\n if (existingSibligCount > 1) {\n $siblings.eq(0).before($nodeChart.siblings().addBack().unwrap());\n } else {\n $siblings.eq(insertPostion).after($nodeChart.unwrap());\n }\n } else { // build the sibling nodes and parent node for the specific ndoe\n this.buildHierarchy($nodeChart.parent().prepend($('
- ')).children('.hierarchy:first'), data);\n $nodeChart.prevAll('.hierarchy').children('.nodes').children().eq(insertPostion).after($nodeChart);\n }\n },\n //\n addSiblings: function ($node, data) {\n this.buildSiblingNode($node.closest('.hierarchy'), data);\n $node.closest('.nodes').data('siblingsLoaded', true);\n if (!$node.children('.leftEdge').length) {\n $node.children('.topEdge').after('');\n }\n if (this.isInAction($node)) {\n this.switchHorizontalArrow($node);\n $node.children('.topEdge').removeClass('oci-chevron-up').addClass('oci-chevron-down');\n }\n },\n // remove node and its descendent nodes\n removeNodes: function ($node) {\n var $wrapper = $node.closest('.hierarchy').parent();\n if ($wrapper.parent().is('.hierarchy')) {\n if (this.getNodeState($node, 'siblings').exist) {\n $node.closest('.hierarchy').remove();\n if ($wrapper.children().length === 1) {\n $wrapper.find('.node:first .horizontalEdge').remove();\n }\n } else {\n $wrapper.siblings('.node').find('.bottomEdge').remove()\n .end().end().remove();\n }\n } else { // if $node is root node\n $wrapper.closest('.orgchart').siblings('.oc-export-btn').addBack().remove();\n }\n },\n //\n hideDropZones: function () {\n // Remove all the 'this is a drop zone' indicators\n var orgChartObj = this;\n orgChartObj.$chart.find('.allowedDrop')\n .removeClass('allowedDrop');\n },\n //\n showDropZones: function (dragged) {\n // Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later\n // TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should!\n var orgChartObj = this;\n orgChartObj.$chart.find('.node')\n .each(function (index, node) {\n $(node).addClass('allowedDrop');\n });\n orgChartObj.$chart.data('dragged', $(dragged));\n },\n //\n processExternalDrop: function (dropZone, dragged) {\n // Allow an external drop event to be handled by one of our nodes\n if (dragged) {\n this.$chart.data('dragged', $(dragged));\n }\n var droppedOnNode = dropZone.closest('.node');\n // would like to just call 'dropZoneHandler', but I can't reach it from here\n // instead raise a drop event on the node element\n droppedOnNode.triggerHandler({ 'type': 'drop' });\n },\n //\n exportPDF: function(canvas, exportFilename){\n var doc = {};\n var docWidth = Math.floor(canvas.width);\n var docHeight = Math.floor(canvas.height);\n if (!window.jsPDF) {\n window.jsPDF = window.jspdf.jsPDF;\n }\n\n if (docWidth > docHeight) {\n doc = new jsPDF({\n orientation: 'landscape',\n unit: 'px',\n format: [docWidth, docHeight]\n });\n } else {\n doc = new jsPDF({\n orientation: 'portrait',\n unit: 'px',\n format: [docHeight, docWidth]\n });\n }\n doc.addImage(canvas.toDataURL(), 'png', 0, 0);\n doc.save(exportFilename + '.pdf');\n },\n //\n exportPNG: function(canvas, exportFilename){\n var that = this;\n var isWebkit = 'WebkitAppearance' in document.documentElement.style;\n var isFf = !!window.sidebar;\n var isEdge = navigator.appName === 'Microsoft Internet Explorer' || (navigator.appName === \"Netscape\" && navigator.appVersion.indexOf('Edge') > -1);\n var $chartContainer = this.$chartContainer;\n\n if ((!isWebkit && !isFf) || isEdge) {\n window.navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png');\n } else {\n var selector = '.oci-download-btn' + (that.options.chartClass !== '' ? '.' + that.options.chartClass : '');\n\n if (!$chartContainer.find(selector).length) {\n $chartContainer.append('');\n }\n\n $chartContainer.find(selector).attr('href', canvas.toDataURL())[0].click();\n }\n },\n //\n export: function (exportFilename, exportFileextension) {\n var that = this;\n exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;\n exportFileextension = (typeof exportFileextension !== 'undefined') ? exportFileextension : this.options.exportFileextension;\n if ($(this).children('.spinner').length) {\n return false;\n }\n var $chartContainer = this.$chartContainer;\n var $mask = $chartContainer.find('.mask');\n if (!$mask.length) {\n $chartContainer.append('
');\n } else {\n $mask.removeClass('hidden');\n }\n var sourceChart = $chartContainer.addClass('canvasContainer').find('.orgchart:not(\".hidden\")').get(0);\n var flag = that.options.direction === 'l2r' || that.options.direction === 'r2l';\n html2canvas(sourceChart, {\n 'width': flag ? sourceChart.clientHeight : sourceChart.clientWidth,\n 'height': flag ? sourceChart.clientWidth : sourceChart.clientHeight,\n 'onclone': function (cloneDoc) {\n $(cloneDoc).find('.canvasContainer').css('overflow', 'visible')\n .find('.orgchart:not(\".hidden\"):first').css('transform', '');\n }\n })\n .then(function (canvas) {\n $chartContainer.find('.mask').addClass('hidden');\n\n if (exportFileextension.toLowerCase() === 'pdf') {\n that.exportPDF(canvas, exportFilename);\n } else {\n that.exportPNG(canvas, exportFilename);\n }\n\n $chartContainer.removeClass('canvasContainer');\n }, function () {\n $chartContainer.removeClass('canvasContainer');\n });\n }\n };\n\n $.fn.orgchart = function (opts) {\n return new OrgChart(this, opts).init();\n };\n\n}));\n"]}
\ No newline at end of file
diff --git a/package.json b/package.json
index e01c5438..729fdfe6 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "orgchart",
- "version": "3.0.2",
+ "version": "3.1.0",
"description": "Simple and direct organization chart(tree-like hierarchy) plugin based on pure DOM and jQuery.",
"main": "./dist/js/jquery.orgchart.min.js",
"style": [
diff --git a/src/js/jquery.orgchart.js b/src/js/jquery.orgchart.js
index 2466732f..f04f5de7 100644
--- a/src/js/jquery.orgchart.js
+++ b/src/js/jquery.orgchart.js
@@ -748,25 +748,38 @@
},
// determines how to show arrow buttons
nodeEnterLeaveHandler: function (event) {
- var $node = $(event.delegateTarget), 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 = this.getNodeState($node, 'parent').visible;
- $topEdge.toggleClass('oci-chevron-up', !flag).toggleClass('oci-chevron-down', flag);
- }
- if ($bottomEdge.length) {
- flag = this.getNodeState($node, 'children').visible;
- $bottomEdge.toggleClass('oci-chevron-down', !flag).toggleClass('oci-chevron-up', flag);
- }
- if ($leftEdge.length) {
- this.switchHorizontalArrow($node);
+ var $node = $(event.delegateTarget);
+ var flag = false;
+ if ($node.closest('.nodes.vertical').length) {
+ var $toggleBtn = $node.children('.toggleBtn');
+ if (event.type === 'mouseenter') {
+ if ($node.children('.toggleBtn').length) {
+ flag = this.getNodeState($node, 'children').visible;
+ $toggleBtn.toggleClass('oci-plus-square', !flag).toggleClass('oci-minus-square', flag);
+ }
+ } else {
+ $toggleBtn.removeClass('oci-plus-square oci-minus-square');
}
} else {
- $node.children('.edge').removeClass('oci-chevron-up oci-chevron-down oci-chevron-right oci-chevron-left');
+ 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 = this.getNodeState($node, 'parent').visible;
+ $topEdge.toggleClass('oci-chevron-up', !flag).toggleClass('oci-chevron-down', flag);
+ }
+ if ($bottomEdge.length) {
+ flag = this.getNodeState($node, 'children').visible;
+ $bottomEdge.toggleClass('oci-chevron-down', !flag).toggleClass('oci-chevron-up', flag);
+ }
+ if ($leftEdge.length) {
+ this.switchHorizontalArrow($node);
+ }
+ } else {
+ $node.children('.edge').removeClass('oci-chevron-up oci-chevron-down oci-chevron-right oci-chevron-left');
+ }
}
},
//
@@ -1270,8 +1283,8 @@
var flags = data.relationship || '';
if (opts.verticalLevel && level >= opts.verticalLevel) {
if ((level + 1) > opts.verticalLevel && Number(flags.substr(2,1))) {
- var icon = level + 1 > opts.visibleLevel ? 'plus' : 'minus';
- $nodeDiv.append('');
+ $nodeDiv.append('')
+ .children('.title').prepend('');
}
} else {
if (Number(flags.substr(0,1))) {
@@ -1368,12 +1381,18 @@
// exposed method
addChildren: function ($node, data) {
this.buildChildNode($node.closest('.hierarchy'), data);
- if (!$node.children('.bottomEdge').length) {
- $node.append('');
- }
if (!$node.find('.symbol').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'));
}
diff --git a/test/e2e/vertical-level/test.js b/test/e2e/vertical-level/test.js
index 8d3a1c21..165e716a 100644
--- a/test/e2e/vertical-level/test.js
+++ b/test/e2e/vertical-level/test.js
@@ -16,11 +16,13 @@ const wudan = page.wudan;
test('toggle the vertical nodes', async t => {
await t
+ .hover(dandan)
.click(dandan.find('.toggleBtn'))
.expect(erdan.visible).ok()
.expect(sandan.visible).ok()
.expect(sidan.visible).notOk()
.expect(wudan.visible).notOk()
+ .hover(heihei)
.click(heihei.find('.toggleBtn'))
.expect(pangpang.visible).notOk()
.expect(dandan.visible).notOk()
diff --git a/test/integration/init-tests.js b/test/integration/init-tests.js
index 1519bf36..f67b68e3 100644
--- a/test/integration/init-tests.js
+++ b/test/integration/init-tests.js
@@ -105,8 +105,8 @@ describe('orgchart -- integration tests', function () {
'Lao Lao