')
.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
var flags = data.relationship || '';
if (opts.verticalLevel && level >= opts.verticalLevel) {
if ((level + 1) > opts.verticalLevel && Number(flags.substr(2,1))) {
$nodeDiv.append('
')
.children('.title').prepend('
');
}
} 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));
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;
if (data.level) {
level = data.level;
} else {
level = data.level = $appendTo.parentsUntil('.orgchart', '.nodes').length;
}
// Construct the node
if (Object.keys(data).length > 2) {
var $nodeDiv = this.createNode(data);
if (opts.verticalLevel && level >= opts.verticalLevel) {
$appendTo.append($nodeDiv);
} else {
$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 isVerticalLayer = opts.verticalLevel && (level + 1) >= opts.verticalLevel;
var $nodesLayer;
if (isVerticalLayer) {
$nodesLayer = $('
');
if (isHidden && level + 1 >= opts.verticalLevel) {
$nodesLayer.addClass('hidden');
}
if (level + 1 === opts.verticalLevel) {
$appendTo.addClass('hybrid').append($nodesLayer.addClass('vertical'));
} else {
$appendTo.append($nodesLayer);
}
} 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 () {
var $nodeCell = $('- ');
$nodesLayer.append($nodeCell);
this.level = level + 1;
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('.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'));
}
},
// 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('oci-chevron-up').addClass('oci-chevron-down');
}
},
// 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 = '.oci-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();
};
}));