/* * jQuery OrgChart Plugin * https://github.com/dabeng/OrgChart * * Copyright 2016, dabeng * https://github.com/dabeng * * 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) { var OrgChart = function (elem, opts) { this.$chartContainer = $(elem); this.opts = opts; this.defaultOptions = { 'nodeTitle': 'name', 'nodeId': 'id', 'toggleSiblingsResp': false, 'visibleLevel': 999, 'chartClass': '', 'exportButton': false, 'exportFilename': 'OrgChart', 'exportFileextension': 'png', 'draggable': false, 'direction': 't2b', 'pan': false, 'zoom': false, 'zoominLimit': 7, 'zoomoutLimit': 0.5 }; }; // OrgChart.prototype = { // init: function (opts) { var that = this; this.options = $.extend({}, this.defaultOptions, this.opts, opts); // build the org-chart var $chartContainer = this.$chartContainer; if (this.$chart) { this.$chart.remove(); } var data = this.options.data; var $chart = this.$chart = $('
', { 'data': { 'options': this.options }, 'class': 'orgchart' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '') + (this.options.direction !== 't2b' ? ' ' + this.options.direction : ''), }); if (typeof MutationObserver !== 'undefined') { this.triggerInitEvent(); } if ($.type(data) === 'object') { if (data instanceof $) { // ul datasource this.buildHierarchy($chart, this.buildJsonDS(data.children()), 0, this.options); } else { // local json datasource this.buildHierarchy($chart, this.options.ajaxURL ? data : this.attachRel(data, '00')); } } else { $chart.append(''); $.ajax({ 'url': data, 'dataType': 'json' }) .done(function(data, textStatus, jqXHR) { that.buildHierarchy($chart, that.options.ajaxURL ? data : that.attachRel(data, '00'), 0, that.options); }) .fail(function(jqXHR, textStatus, errorThrown) { console.log(errorThrown); }) .always(function() { $chart.children('.spinner').remove(); }); } $chartContainer.append($chart); if (this.options.pan) { this.bindPan(); } if (this.options.zoom) { this.bindZoom(); } return this; }, // triggerInitEvent: function () { var that = this; var mo = new MutationObserver(function (mutations) { mo.disconnect(); initTime: for (var i = 0; i < mutations.length; i++) { for (var j = 0; j < mutations[i].addedNodes.length; j++) { if (mutations[i].addedNodes[j].classList.contains('orgchart')) { if (that.options.initCompleted && typeof that.options.initCompleted === 'function') { that.options.initCompleted(that.$chart); var initEvent = $.Event('init.orgchart'); that.$chart.trigger(initEvent); break initTime; } } } } }); mo.observe(this.$chartContainer[0], { childList: true }); }, // panStartHandler: function (e) { var $chart = $(e.delegateTarget); if ($(e.target).closest('.node').length || (e.touches && e.touches.length > 1)) { $chart.data('panning', false); return; } else { $chart.css('cursor', 'move').data('panning', true); } var lastX = 0; var lastY = 0; var lastTf = $chart.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 = 0; var startY = 0; if (!e.targetTouches) { // pand on desktop startX = e.pageX - lastX; startY = e.pageY - lastY; } else if (e.targetTouches.length === 1) { // pan on mobile device startX = e.targetTouches[0].pageX - lastX; startY = e.targetTouches[0].pageY - lastY; } else if (e.targetTouches.length > 1) { return; } $chart.on('mousemove touchmove',function(e) { if (!$chart.data('panning')) { return; } var newX = 0; var newY = 0; if (!e.targetTouches) { // pand on desktop newX = e.pageX - startX; newY = e.pageY - startY; } else if (e.targetTouches.length === 1) { // pan on mobile device newX = e.targetTouches[0].pageX - startX; newY = e.targetTouches[0].pageY - startY; } else if (e.targetTouches.length > 1) { return; } var lastTf = $chart.css('transform'); if (lastTf === 'none') { if (lastTf.indexOf('3d') === -1) { $chart.css('transform', 'matrix(1, 0, 0, 1, ' + newX + ', ' + newY + ')'); } else { $chart.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; } $chart.css('transform', matrix.join(',')); } }); }, // panEndHandler: function (e) { if (e.data.chart.data('panning')) { e.data.chart.data('panning', false).css('cursor', 'default').off('mousemove'); } }, // bindPan: function () { this.$chartContainer.css('overflow', 'hidden'); this.$chart.on('mousedown touchstart', this.panStartHandler); $(document).on('mouseup touchend', { 'chart': this.$chart }, this.panEndHandler); }, // unbindPan: function () { this.$chartContainer.css('overflow', 'auto'); this.$chart.off('mousedown touchstart', this.panStartHandler); $(document).off('mouseup touchend', this.panEndHandler); }, // zoomWheelHandler: function (e) { var oc = e.data.oc; e.preventDefault(); var newScale = 1 + (e.originalEvent.deltaY > 0 ? -0.2 : 0.2); oc.setChartScale(oc.$chart, newScale); }, // zoomStartHandler: function (e) { if(e.touches && e.touches.length === 2) { var oc = e.data.oc; oc.$chart.data('pinching', true); var dist = oc.getPinchDist(e); oc.$chart.data('pinchDistStart', dist); } }, zoomingHandler: function (e) { var oc = e.data.oc; if(oc.$chart.data('pinching')) { var dist = oc.getPinchDist(e); oc.$chart.data('pinchDistEnd', dist); } }, zoomEndHandler: function (e) { var oc = e.data.oc; if(oc.$chart.data('pinching')) { oc.$chart.data('pinching', false); var diff = oc.$chart.data('pinchDistEnd') - oc.$chart.data('pinchDistStart'); if (diff > 0) { oc.setChartScale(oc.$chart, 1.2); } else if (diff < 0) { oc.setChartScale(oc.$chart, 0.8); } } }, // bindZoom: function () { this.$chartContainer.on('wheel', { 'oc': this }, this.zoomWheelHandler); this.$chartContainer.on('touchstart', { 'oc': this }, this.zoomStartHandler); $(document).on('touchmove', { 'oc': this }, this.zoomingHandler); $(document).on('touchend', { 'oc': this }, this.zoomEndHandler); }, unbindZoom: function () { this.$chartContainer.off('wheel', this.zoomWheelHandler); this.$chartContainer.off('touchstart', this.zoomStartHandler); $(document).off('touchmove', this.zoomingHandler); $(document).off('touchend', this.zoomEndHandler); }, // getPinchDist: function (e) { return Math.sqrt((e.touches[0].clientX - e.touches[1].clientX) * (e.touches[0].clientX - e.touches[1].clientX) + (e.touches[0].clientY - e.touches[1].clientY) * (e.touches[0].clientY - e.touches[1].clientY)); }, // setChartScale: function ($chart, newScale) { var opts = $chart.data('options'); var lastTf = $chart.css('transform'); var matrix = ''; var targetScale = 1; if (lastTf === 'none') { $chart.css('transform', 'scale(' + newScale + ',' + newScale + ')'); } else { matrix = lastTf.split(','); if (lastTf.indexOf('3d') === -1) { targetScale = Math.abs(window.parseFloat(matrix[3]) * newScale); if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) { $chart.css('transform', lastTf + ' scale(' + newScale + ',' + newScale + ')'); } } else { targetScale = Math.abs(window.parseFloat(matrix[1]) * newScale); if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) { $chart.css('transform', lastTf + ' scale3d(' + newScale + ',' + newScale + ', 1)'); } } } }, // buildJsonDS: function ($li) { var that = this; 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) }; $.each($li.data(), function(key, value) { subObj[key] = value; }); $li.children('ul').children().each(function() { if (!subObj.children) { subObj.children = []; } subObj.children.push(that.buildJsonDS($(this))); }); return subObj; }, // attachRel: function (data, flags) { var that = this; data.relationship = flags + (data.children && data.children.length > 0 ? 1 : 0); if (data.children) { data.children.forEach(function(item) { that.attachRel(item, '1' + (data.children.length > 1 ? 1 : 0)); }); } return data; }, // loopChart: function ($chart) { var that = this; 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(that.loopChart($(this))); }); return subObj; }, // getHierarchy: function () { if (typeof this.$chart === 'undefined') { return 'Error: orgchart does not exist' } else { if (!this.$chart.find('.node').length) { return 'Error: nodes do not exist' } else { var valid = true; this.$chart.find('.node').each(function () { if (!this.id) { valid = false; return false; } }); if (!valid) { return 'Error: All nodes of orghcart to be exported must have data-id attribute!'; } } } return this.loopChart(this.$chart); }, // detect the exist/display state of related node getNodeState: function ($node, relation) { var $target = {}; var relation = relation || 'self'; if (relation === 'parent') { $target = $node.closest('.nodes').siblings(':first'); if ($target.length) { if ($target.is('.hidden') || (!$target.is('.hidden') && $target.closest('.nodes').is('.hidden'))) { return { 'exist': true, 'visible': false }; } return { 'exist': true, 'visible': true }; } } else if (relation === 'children') { $target = $node.closest('tr').siblings(':last'); if ($target.length) { if (!$target.is('.hidden')) { return { 'exist': true, 'visible': true }; } return { 'exist': true, 'visible': false }; } } else if (relation === 'siblings') { $target = $node.closest('table').parent().siblings(); if ($target.length) { if (!$target.is('.hidden') && !$target.parent().is('.hidden')) { return { 'exist': true, 'visible': true }; } return { 'exist': true, 'visible': false }; } } else { $target = $node; if ($target.length) { if (!(($target.closest('.nodes').length && $target.closest('.nodes').is('.hidden')) || ($target.closest('table').parent().length && $target.closest('table').parent().is('.hidden')) || ($target.parent().is('li') && ($target.closest('ul').is('.hidden') || $target.closest('verticalNodes').is('.hidden'))) )) { return { 'exist': true, 'visible': true }; } return { 'exist': true, 'visible': false }; } } return { 'exist': false, 'visible': false }; }, // find the related nodes getRelatedNodes: function ($node, relation) { if (!$node || !($node instanceof $) || !$node.is('.node')) { return $(); } if (relation === 'parent') { return $node.closest('.nodes').parent().children(':first').find('.node'); } else if (relation === 'children') { return $node.closest('tr').siblings('.nodes').children().find('.node:first'); } else if (relation === 'siblings') { return $node.closest('table').parent().siblings().find('.node:first'); } else { return $(); } }, // show the parent node of the specified node showParent: function ($node) { // just show only one superior level var $upperLevel = $node.closest('.nodes').siblings().removeClass('hidden'); // just show only one line $upperLevel.eq(2).children().slice(1, -1).addClass('hidden'); // show parent node with animation var $parent = $upperLevel.eq(0).find('.node'); this.repaint($parent[0]); $parent.addClass('sliding').removeClass('slide-down').one('transitionend', { 'node': $node }, this.showParentEnd.bind(this)); }, stopAjax: function ($nodeLevel) { if ($nodeLevel.find('.spinner').length) { $nodeLevel.closest('.orgchart').data('inAjax', false); } }, isVisibleNode: function (index, elem) { return this.getNodeState($(elem)).visible; }, // // start up loading status for requesting new nodes startLoading: function ($edge) { var $chart = this.$chart; if (typeof $chart.data('inAjax') !== 'undefined' && $chart.data('inAjax') === true) { return false; } $edge.addClass('hidden'); $edge.parent().append('') .children().not('.spinner').css('opacity', 0.2); $chart.data('inAjax', true); $('.oc-export-btn' + (this.options.chartClass !== '' ? '.' + this.options.chartClass : '')).prop('disabled', true); return true; }, // terminate loading status for requesting new nodes endLoading: function ($edge) { var $node = $edge.parent(); $edge.removeClass('hidden'); $node.find('.spinner').remove(); $node.children().removeAttr('style'); this.$chart.data('inAjax', false); $('.oc-export-btn' + (this.options.chartClass !== '' ? '.' + this.options.chartClass : '')).prop('disabled', false); }, // whether the cursor is hovering over the node isInAction: function ($node) { return $node.children('.edge').attr('class').indexOf('fa-') > -1 ? true : false; }, // switchVerticalArrow: function ($arrow) { $arrow.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down'); }, // switchHorizontalArrow: function ($node) { var opts = this.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); } }, // repaint: function (node) { if (node) { node.style.offsetWidth = node.offsetWidth; } }, // load new nodes by ajax loadNodes: function (rel, url, $edge) { var that = this; var opts = this.options; $.ajax({ 'url': url, 'dataType': 'json' }) .done(function (data) { if (that.$chart.data('inAjax')) { if (rel === 'parent') { if (!$.isEmptyObject(data)) { that.addParent($edge.parent(), data); } } else if (rel === 'children') { if (data.children.length) { that.addChildren($edge.parent(), data[rel]); } } else { that.addSiblings($edge.parent(), data.siblings ? data.siblings : data); } } }) .fail(function () { console.log('Failed to get ' + rel + ' data'); }) .always(function () { that.endLoading($edge); }); }, // HideFirstParentEnd: function (event) { var $topEdge = event.data.topEdge; var $node = $topEdge.parent(); if (this.isInAction($node)) { this.switchVerticalArrow($topEdge); this.switchHorizontalArrow($node); } }, // expandVNodesEnd: function (event) { event.data.vNodes.removeClass('sliding'); }, // collapseVNodesEnd: function (event) { event.data.vNodes.removeClass('sliding').closest('ul').addClass('hidden'); }, // createGhostNode: function (event) { var $nodeDiv = $(event.target); var opts = this.options; var origEvent = event.originalEvent; var isFirefox = /firefox/.test(window.navigator.userAgent.toLowerCase()); var ghostNode, nodeCover; if (!document.querySelector('.ghost-node')) { ghostNode = document.createElementNS("http://www.w3.org/2000/svg", "svg"); ghostNode.classList.add('ghost-node'); nodeCover = document.createElementNS('http://www.w3.org/2000/svg','rect'); ghostNode.appendChild(nodeCover); $nodeDiv.closest('.orgchart').append(ghostNode); } else { ghostNode = $nodeDiv.closest('.orgchart').children('.ghost-node').get(0); nodeCover = $(ghostNode).children().get(0); } var transValues = $nodeDiv.closest('.orgchart').css('transform').split(','); var isHorizontal = opts.direction === 't2b' || opts.direction === 'b2t'; var scale = Math.abs(window.parseFloat(isHorizontal ? transValues[0].slice(transValues[0].indexOf('(') + 1) : transValues[1])); ghostNode.setAttribute('width', isHorizontal ? $nodeDiv.outerWidth(false) : $nodeDiv.outerHeight(false)); ghostNode.setAttribute('height', isHorizontal ? $nodeDiv.outerHeight(false) : $nodeDiv.outerWidth(false)); nodeCover.setAttribute('x',5 * scale); nodeCover.setAttribute('y',5 * scale); nodeCover.setAttribute('width', 120 * scale); nodeCover.setAttribute('height', 40 * scale); nodeCover.setAttribute('rx', 4 * scale); nodeCover.setAttribute('ry', 4 * scale); nodeCover.setAttribute('stroke-width', 1 * scale); var xOffset = origEvent.offsetX * scale; var yOffset = origEvent.offsetY * scale; if (opts.direction === 'l2r') { xOffset = origEvent.offsetY * scale; yOffset = origEvent.offsetX * scale; } else if (opts.direction === 'r2l') { xOffset = $nodeDiv.outerWidth(false) - origEvent.offsetY * scale; yOffset = origEvent.offsetX * scale; } else if (opts.direction === 'b2t') { xOffset = $nodeDiv.outerWidth(false) - origEvent.offsetX * scale; yOffset = $nodeDiv.outerHeight(false) - origEvent.offsetY * scale; } if (isFirefox) { // hack for old version of Firefox(< 48.0) nodeCover.setAttribute('fill', 'rgb(255, 255, 255)'); nodeCover.setAttribute('stroke', 'rgb(191, 0, 0)'); var ghostNodeWrapper = document.createElement('img'); ghostNodeWrapper.src = 'data:image/svg+xml;utf8,' + (new XMLSerializer()).serializeToString(ghostNode); origEvent.dataTransfer.setDragImage(ghostNodeWrapper, xOffset, yOffset); } else { origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset); } }, // filterAllowedDropNodes: function ($dragged) { var opts = this.options; var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); var $dragHier = $dragged.closest('table').find('.node'); this.$chart.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'); } } }); }, // dragstartHandler: function (event) { event.originalEvent.dataTransfer.setData('text/html', 'hack for firefox'); // if users enable zoom or direction options if (this.$chart.css('transform') !== 'none') { this.createGhostNode(event); } this.filterAllowedDropNodes($(event.target)); }, // dragoverHandler: function (event) { event.preventDefault(); if (!$(event.delegateTarget).is('.allowedDrop')) { event.originalEvent.dataTransfer.dropEffect = 'none'; } }, // dragendHandler: function (event) { this.$chart.find('.allowedDrop').removeClass('allowedDrop'); }, // dropHandler: function (event) { var $dropZone = $(event.delegateTarget); var $dragged = this.$chart.data('dragged'); var $dragZone = $dragged.closest('.nodes').siblings().eq(0).children(); var dropEvent = $.Event('nodedrop.orgchart'); this.$chart.trigger(dropEvent, { 'draggedNode': $dragged, 'dragZone': $dragZone.children(), 'dropZone': $dropZone }); if (dropEvent.isDefaultPrevented()) { return; } // firstly, deal with the hierarchy of drop zone if (!$dropZone.closest('tr').siblings().length) { // if the drop zone is a leaf node $dropZone.append('') .parent().attr('colspan', 2) .parent().after('
' + '' + '') .siblings(':last').append($dragged.find('.horizontalEdge').remove().end().closest('table').parent()); } else { var dropColspan = parseInt($dropZone.parent().attr('colspan')) + 2; var horizontalEdges = ''; $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('') .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(); } }, // touchstartHandler: function (event) { console.log("orgChart: touchstart 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", target=" + event.target.innerText); if (this.touchHandled) return; this.touchHandled = true; this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch event.preventDefault(); }, // touchmoveHandler: function (event) { if (!this.touchHandled) return; event.preventDefault(); if (!this.touchMoved) { var nodeIsSelected = $(this).hasClass('focused'); console.log("orgChart: touchmove 1: " + event.touches.length + " touches, we have not moved, so simulate a drag start", event.touches); // TODO: visualise the start of the drag (as would happen on desktop) this.simulateMouseEvent(event, 'dragstart'); } this.touchMoved = true; var $touching = $(document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY)); var $touchingNode = $touching.closest('div.node'); if ($touchingNode.length > 0) { var touchingNodeElement = $touchingNode[0]; // TODO: simulate the dragover visualisation if ($touchingNode.is('.allowedDrop')) { console.log("orgChart: touchmove 2: this node (" + touchingNodeElement.id + ") is allowed to be a drop target"); this.touchTargetNode = touchingNodeElement; } else { console.log("orgChart: touchmove 3: this node (" + touchingNodeElement.id + ") is NOT allowed to be a drop target"); this.touchTargetNode = null; } } else { console.log("orgchart: touchmove 4: not touching a node"); this.touchTargetNode = null; } }, // touchendHandler: function (event) { console.log("orgChart: touchend 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", " + event.target.innerText + " "); if (!this.touchHandled) { console.log("orgChart: touchend 2: not handled by us, so aborting"); return; } if (this.touchMoved) { // we've had movement, so this was a 'drag' touch if (this.touchTargetNode) { console.log("orgChart: touchend 3: moved to a node, so simulating drop"); var fakeEventForDropHandler = { delegateTarget: this.touchTargetNode }; this.dropHandler(fakeEventForDropHandler); this.touchTargetNode = null; } console.log("orgChart: touchend 4: simulating dragend"); this.simulateMouseEvent(event, 'dragend'); } else { // we did not move, so assume this was a 'press' touch console.log("orgChart: touchend 5: moved, so simulating click"); this.simulateMouseEvent(event, 'click'); } this.touchHandled = false; }, // simulate a mouse event (so we can fake them on a touch device) simulateMouseEvent: function (event, simulatedType) { // Ignore multi-touch events if (event.originalEvent.touches.length > 1) { return; } var touch = event.originalEvent.changedTouches[0]; var simulatedEvent = document.createEvent('MouseEvents'); simulatedEvent.initMouseEvent( simulatedType, // type true, // bubbles true, // cancelable window, // view 1, // detail touch.screenX, // screenX touch.screenY, // screenY touch.clientX, // clientX touch.clientY, // clientY false, // ctrlKey false, // altKey false, // shiftKey false, // metaKey 0, // button null // relatedTarget ); // Dispatch the simulated event to the target element event.target.dispatchEvent(simulatedEvent); }, // bindDragDrop: function ($node) { $node.on('dragstart', this.dragstartHandler.bind(this)) .on('dragover', this.dragoverHandler.bind(this)) .on('dragend', this.dragendHandler.bind(this)) .on('drop', this.dropHandler.bind(this)) .on('touchstart', this.touchstartHandler.bind(this)) .on('touchmove', this.touchmoveHandler.bind(this)) .on('touchend', this.touchendHandler.bind(this)); }, // create node createNode: function (data) { var that = this; var opts = this.options; var level = data.level; if (data.children) { $.each(data.children, function (index, child) { child.parentId = data.id; }); } // construct the content of node var $nodeDiv = $('') .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))) { var icon = level + 1 > opts.visibleLevel ? 'plus' : 'minus'; $nodeDiv.append(''); } } 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(''); } } 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 + 1; } // Construct the node var childrenData = data.children; var hasChildren = childrenData ? childrenData.length : false; var $nodeWrapper; if (Object.keys(data).length > 2) { var $nodeDiv = this.createNode(data); if (opts.verticalLevel && level >= opts.verticalLevel) { $appendTo.append($nodeDiv); }else { $nodeWrapper = $(''); $appendTo.append($nodeWrapper.append($('').append($('').append($nodeDiv)))); } } // Construct the lower level(two "connectiong lines" rows and "inferior nodes" row) if (hasChildren) { var isHidden = (level + 1 > opts.visibleLevel || data.collapsed) ? ' hidden' : ''; var isVerticalLayer = (opts.verticalLevel && (level + 1) >= opts.verticalLevel) ? true : false; var $nodesLayer; if (isVerticalLayer) { $nodesLayer = $('
    '); if (isHidden && level + 1 > opts.verticalLevel) { $nodesLayer.addClass(isHidden); } if (level + 1 === opts.verticalLevel) { $appendTo.children('table').append('
') .find('.verticalNodes').children().append($nodesLayer); } else { $appendTo.append($nodesLayer); } } else { var $upperLines = $(''); var lowerLines = ''; for (var i=1; i'); if (Object.keys(data).length === 2) { $appendTo.append($upperLines).append(lowerLines).append($nodesLayer); } else { $nodeWrapper.append($upperLines).append(lowerLines).append($nodesLayer); } } // recurse through children nodes $.each(childrenData, function () { var $nodeCell = isVerticalLayer ? $('
  • ') : $('
  • '); $nodesLayer.append($nodeCell); this.level = level + 1; that.buildHierarchy($nodeCell, this); }); } }, // build the child nodes of specific node buildChildNode: function ($appendTo, data) { $appendTo.find('td:first').attr('colspan', data.length * 2); this.buildHierarchy($appendTo, { 'children': data }); }, // exposed method addChildren: function ($node, data) { this.buildChildNode($node.closest('table'), data); if (!$node.children('.bottomEdge').length) { $node.append(''); } if (!$node.find('.symbol').length) { $node.children('.title').prepend(''); } 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 $table = $('') .append($('').append($('') .append(''); this.$chart.prepend($table) .children('table:first').append('') .children('tr:last').children().append(this.$chart.children('table').last()); }, // 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')); } }, // subsequent processing of build sibling nodes complementLine: function ($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 buildSiblingNode: function ($nodeChart, data) { var newSiblingCount = $.isArray(data) ? data.length : data.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(); this.buildChildNode($nodeChart.parent().closest('table'), data); var $siblingTds = $nodeChart.parent().closest('table').children('tr:last').children('td'); if (existingSibligCount > 1) { this.complementLine($siblingTds.eq(0).before($nodeChart.closest('td').siblings().addBack().unwrap()), siblingCount, existingSibligCount); } else { this.complementLine($siblingTds.eq(insertPostion).after($nodeChart.closest('td').unwrap()), siblingCount, 1); } } else { // build the sibling nodes and parent node for the specific ndoe this.buildHierarchy($nodeChart.closest('.orgchart'), data); this.complementLine($nodeChart.next().children('tr:last').children().eq(insertPostion).after($('
    ').append(this.createNode(data)))) .append('
    ').append($nodeChart)), siblingCount, 1); } }, // addSiblings: function ($node, data) { this.buildSiblingNode($node.closest('table'), 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('fa-chevron-up').addClass('fa-chevron-down'); } }, // removeNodes: function ($node) { var $parent = $node.closest('table').parent(); var $sibs = $parent.parent().siblings(); if ($parent.is('td')) { if (this.getNodeState($node, 'siblings').exist) { $sibs.eq(2).children('.lineTop:lt(2)').remove(); $sibs.slice(0, 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(); } }, // 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', ''); }, 'onrendered': function (canvas) { $chartContainer.find('.mask').addClass('hidden'); if (exportFileextension.toLowerCase() === 'pdf') { var doc = {}; var docWidth = Math.floor(canvas.width * 0.2646); var docHeight = Math.floor(canvas.height * 0.2646); if (docWidth > docHeight) { doc = new jsPDF('l', 'mm', [docWidth, docHeight]); } else { doc = new jsPDF('p', 'mm', [docHeight, docWidth]); } doc.addImage(canvas.toDataURL(), 'png', 0, 0); doc.save(exportFilename + '.pdf'); } else { 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); if ((!isWebkit && !isFf) || isEdge) { window.navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png'); } else { var selector = '.oc-download-btn' + (that.options.chartClass !== '' ? '.' + that.options.chartClass : ''); if (!$chartContainer.find(selector).length) { $chartContainer.append(''); } $chartContainer.find(selector).attr('href', canvas.toDataURL())[0].click(); } } } }) .then(function () { $chartContainer.removeClass('canvasContainer'); }, function () { $chartContainer.removeClass('canvasContainer'); }); } }; $.fn.orgchart = function (opts) { return new OrgChart(this, opts).init(); }; }));