| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466 |
- /*
- * 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',
- 'parentNodeSymbol': 'fa-users',
- 'draggable': false,
- 'direction': 't2b',
- 'pan': false,
- 'zoom': false,
- 'zoominLimit': 7,
- 'zoomoutLimit': 0.5,
- 'exportDataInput':false,
- };
- };
- //
- 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 = $('<div>', {
- 'data': { 'options': this.options },
- 'class': 'orgchart' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '') + (this.options.direction !== 't2b' ? ' ' + this.options.direction : ''),
- 'click': function(event) {
- if (!$(event.target).closest('.node').length) {
- $chart.find('.node.focused').removeClass('focused');
- }
- }
- });
- 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('<i class="fa fa-circle-o-notch fa-spin spinner"></i>');
- $.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);
- // append the export button
- if (this.options.exportButton && !$chartContainer.find('.oc-export-btn').length) {
- this.attachExportButton();
- }
- if (this.options.pan) {
- this.bindPan();
- }
- if (this.options.zoom) {
- this.bindZoom();
- }
- if (this.options.exportDataInput) {
- this.exportData();
- }
- 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 });
- },
- //
- attachExportButton: function () {
- var that = this;
- var $exportBtn = $('<button>', {
- 'class': 'oc-export-btn' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : ''),
- 'text': 'Export',
- 'click': function(e) {
- e.preventDefault();
- that.export();
- }
- });
- this.$chartContainer.append($exportBtn);
- },
- setOptions: function (opts, val) {
- if (typeof opts === 'string') {
- if (opts === 'pan') {
- if (val) {
- this.bindPan();
- } else {
- this.unbindPan();
- }
- }
- if (opts === 'zoom') {
- if (val) {
- this.bindZoom();
- } else {
- this.unbindZoom();
- }
- }
- }
- if (typeof opts === 'object') {
- if (opts.data) {
- this.init(opts);
- } else {
- if (typeof opts.pan !== 'undefined') {
- if (opts.pan) {
- this.bindPan();
- } else {
- this.unbindPan();
- }
- }
- if (typeof opts.zoom !== 'undefined') {
- if (opts.zoom) {
- this.bindZoom();
- } else {
- this.unbindZoom();
- }
- }
- }
- }
- return this;
- },
- //
- 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 $();
- }
- },
- hideParentEnd: function (event) {
- $(event.target).removeClass('sliding');
- event.data.upperLevel.addClass('hidden').slice(1).removeAttr('style');
- },
- // recursively hide the ancestor node and sibling nodes of the specified node
- hideParent: function ($node) {
- var $upperLevel = $node.closest('.nodes').siblings();
- if ($upperLevel.eq(0).find('.spinner').length) {
- $node.closest('.orgchart').data('inAjax', false);
- }
- // hide the sibling nodes
- if (this.getNodeState($node, 'siblings').visible) {
- this.hideSiblings($node);
- }
- // hide the lines
- var $lines = $upperLevel.slice(1);
- $lines.css('visibility', 'hidden');
- // hide the superior nodes with transition
- var $parent = $upperLevel.eq(0).find('.node');
- if (this.getNodeState($parent).visible) {
- $parent.addClass('sliding slide-down').one('transitionend', { 'upperLevel': $upperLevel }, this.hideParentEnd);
- }
- // if the current node has the parent node, hide it recursively
- if (this.getNodeState($parent, 'parent').visible) {
- this.hideParent($parent);
- }
- },
- showParentEnd: function (event) {
- var $node = event.data.node;
- $(event.target).removeClass('sliding');
- if (this.isInAction($node)) {
- this.switchVerticalArrow($node.children('.topEdge'));
- }
- },
- // 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;
- },
- //
- hideChildrenEnd: function (event) {
- var $node = event.data.node;
- event.data.animatedNodes.removeClass('sliding');
- if (event.data.isVerticalDesc) {
- event.data.lowerLevel.addClass('hidden');
- } else {
- event.data.animatedNodes.closest('.nodes').prevAll('.lines').removeAttr('style').addBack().addClass('hidden');
- event.data.lowerLevel.last().find('.verticalNodes').addClass('hidden');
- }
- if (this.isInAction($node)) {
- this.switchVerticalArrow($node.children('.bottomEdge'));
- }
- },
- // recursively hide the descendant nodes of the specified node
- hideChildren: function ($node) {
- var $lowerLevel = $node.closest('tr').siblings();
- this.stopAjax($lowerLevel.last());
- var $animatedNodes = $lowerLevel.last().find('.node').filter(this.isVisibleNode.bind(this));
- var isVerticalDesc = $lowerLevel.last().is('.verticalNodes') ? true : false;
- if (!isVerticalDesc) {
- $animatedNodes.closest('table').closest('tr').prevAll('.lines').css('visibility', 'hidden');
- }
- this.repaint($animatedNodes.get(0));
- $animatedNodes.addClass('sliding slide-up').eq(0).one('transitionend', { 'animatedNodes': $animatedNodes, 'lowerLevel': $lowerLevel, 'isVerticalDesc': isVerticalDesc, 'node': $node }, this.hideChildrenEnd.bind(this));
- },
- //
- showChildrenEnd: function (event) {
- var $node = event.data.node;
- event.data.animatedNodes.removeClass('sliding');
- if (this.isInAction($node)) {
- this.switchVerticalArrow($node.children('.bottomEdge'));
- }
- },
- // show the children nodes of the specified node
- showChildren: function ($node) {
- var that = this;
- var $levels = $node.closest('tr').siblings();
- var isVerticalDesc = $levels.is('.verticalNodes') ? true : false;
- var $animatedNodes = isVerticalDesc
- ? $levels.removeClass('hidden').find('.node').filter(this.isVisibleNode.bind(this))
- : $levels.removeClass('hidden').eq(2).children().find('.node:first').filter(this.isVisibleNode.bind(this));
- // the two following statements are used to enforce browser to repaint
- this.repaint($animatedNodes.get(0));
- $animatedNodes.addClass('sliding').removeClass('slide-up').eq(0).one('transitionend', { 'node': $node, 'animatedNodes': $animatedNodes }, this.showChildrenEnd.bind(this));
- },
- //
- hideSiblingsEnd: function (event) {
- var $node = event.data.node;
- var $nodeContainer = event.data.nodeContainer;
- var direction = event.data.direction;
- event.data.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');
- event.data.animatedNodes.removeClass('sliding');
- $siblings.find('.node:gt(0)').filter(this.isVisibleNode.bind(this))
- .removeClass('slide-left slide-right').addClass('slide-up');
- $siblings.find('.lines, .nodes, .verticalNodes').addClass('hidden')
- .end().addClass('hidden');
- if (this.isInAction($node)) {
- this.switchHorizontalArrow($node);
- }
- },
- // hide the sibling nodes of the specified node
- hideSiblings: function ($node, direction) {
- var that = this;
- 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').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-right');
- } else {
- $nodeContainer.nextAll().find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-left');
- }
- } else {
- $nodeContainer.prevAll().find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-right');
- $nodeContainer.nextAll().find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-left');
- }
- var $animatedNodes = $nodeContainer.siblings().find('.sliding');
- var $lines = $animatedNodes.closest('.nodes').prevAll('.lines').css('visibility', 'hidden');
- $animatedNodes.eq(0).one('transitionend', { 'node': $node, 'nodeContainer': $nodeContainer, 'direction': direction, 'animatedNodes': $animatedNodes, 'lines': $lines }, this.hideSiblingsEnd.bind(this));
- },
- //
- showSiblingsEnd: function (event) {
- var $node = event.data.node;
- event.data.visibleNodes.removeClass('sliding');
- if (this.isInAction($node)) {
- this.switchHorizontalArrow($node);
- $node.children('.topEdge').removeClass('fa-chevron-up').addClass('fa-chevron-down');
- }
- },
- //
- showRelatedParentEnd: function(event) {
- $(event.target).removeClass('sliding');
- },
- // show the sibling nodes of the specified node
- showSiblings: function ($node, direction) {
- var that = this;
- // 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 (!this.getNodeState($node, 'parent').visible) {
- $upperLevel.removeClass('hidden');
- var parent = $upperLevel.find('.node')[0];
- this.repaint(parent);
- $(parent).addClass('sliding').removeClass('slide-down').one('transitionend', this.showRelatedParentEnd);
- }
- // lastly, show the sibling nodes with animation
- var $visibleNodes = $siblings.find('.node').filter(this.isVisibleNode.bind(this));
- this.repaint($visibleNodes.get(0));
- $visibleNodes.addClass('sliding').removeClass('slide-left slide-right');
- $visibleNodes.eq(0).one('transitionend', { 'node': $node, 'visibleNodes': $visibleNodes }, this.showSiblingsEnd.bind(this));
- },
- // 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('<i class="fa fa-circle-o-notch fa-spin spinner"></i>')
- .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;
- }
- },
- //
- 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('fa-chevron-up', !flag).toggleClass('fa-chevron-down', flag);
- }
- if ($bottomEdge.length) {
- flag = this.getNodeState($node, 'children').visible;
- $bottomEdge.toggleClass('fa-chevron-down', !flag).toggleClass('fa-chevron-up', flag);
- }
- if ($leftEdge.length) {
- this.switchHorizontalArrow($node);
- }
- } else {
- $node.children('.edge').removeClass('fa-chevron-up fa-chevron-down fa-chevron-right fa-chevron-left');
- }
- },
- //
- nodeClickHandler: function (event) {
- this.$chart.find('.focused').removeClass('focused');
- $(event.delegateTarget).addClass('focused');
- },
- // 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);
- }
- },
- //
- topEdgeClickHandler: function (event) {
- event.stopPropagation();
- var that = this;
- var $topEdge = $(event.target);
- var $node = $(event.delegateTarget);
- var parentState = this.getNodeState($node, 'parent');
- if (parentState.exist) {
- var $parent = $node.closest('table').closest('tr').siblings(':first').find('.node');
- if ($parent.is('.sliding')) { return; }
- // hide the ancestor nodes and sibling nodes of the specified node
- if (parentState.visible) {
- this.hideParent($node);
- $parent.one('transitionend', { 'topEdge': $topEdge }, this.HideFirstParentEnd.bind(this));
- } else { // show the ancestors and siblings
- this.showParent($node);
- }
- } else { // load the new parent node of the specified node by ajax request
- // start up loading status
- if (this.startLoading($topEdge)) {
- var opts = this.options;
- var url = $.isFunction(opts.ajaxURL.parent) ? opts.ajaxURL.parent($node.data('nodeData')) : opts.ajaxURL.parent + $node[0].id;
- this.loadNodes('parent', url, $topEdge);
- }
- }
- },
- //
- bottomEdgeClickHandler: function (event) {
- event.stopPropagation();
- var $bottomEdge = $(event.target);
- var $node = $(event.delegateTarget);
- var childrenState = this.getNodeState($node, 'children');
- if (childrenState.exist) {
- var $children = $node.closest('tr').siblings(':last');
- if ($children.find('.sliding').length) { return; }
- // hide the descendant nodes of the specified node
- if (childrenState.visible) {
- this.hideChildren($node);
- } else { // show the descendants
- this.showChildren($node);
- }
- } else { // load the new children nodes of the specified node by ajax request
- if (this.startLoading($bottomEdge)) {
- var opts = this.options;
- var url = $.isFunction(opts.ajaxURL.children) ? opts.ajaxURL.children($node.data('nodeData')) : opts.ajaxURL.children + $node[0].id;
- this.loadNodes('children', url, $bottomEdge);
- }
- }
- },
- //
- hEdgeClickHandler: function (event) {
- event.stopPropagation();
- var $hEdge = $(event.target);
- var $node = $(event.delegateTarget);
- var opts = this.options;
- var siblingsState = this.getNodeState($node, 'siblings');
- if (siblingsState.exist) {
- var $siblings = $node.closest('table').parent().siblings();
- if ($siblings.find('.sliding').length) { return; }
- if (opts.toggleSiblingsResp) {
- var $prevSib = $node.closest('table').parent().prev();
- var $nextSib = $node.closest('table').parent().next();
- if ($hEdge.is('.leftEdge')) {
- if ($prevSib.is('.hidden')) {
- this.showSiblings($node, 'left');
- } else {
- this.hideSiblings($node, 'left');
- }
- } else {
- if ($nextSib.is('.hidden')) {
- this.showSiblings($node, 'right');
- } else {
- this.hideSiblings($node, 'right');
- }
- }
- } else {
- if (siblingsState.visible) {
- this.hideSiblings($node);
- } else {
- this.showSiblings($node);
- }
- }
- } else {
- // load the new sibling nodes of the specified node by ajax request
- if (this.startLoading($hEdge)) {
- var nodeId = $node[0].id;
- var url = (this.getNodeState($node, 'parent').exist) ?
- ($.isFunction(opts.ajaxURL.siblings) ? opts.ajaxURL.siblings($node.data('nodeData')) : opts.ajaxURL.siblings + nodeId) :
- ($.isFunction(opts.ajaxURL.families) ? opts.ajaxURL.families($node.data('nodeData')) : opts.ajaxURL.families + nodeId);
- this.loadNodes('siblings', url, $hEdge);
- }
- }
- },
- //
- expandVNodesEnd: function (event) {
- event.data.vNodes.removeClass('sliding');
- },
- //
- collapseVNodesEnd: function (event) {
- event.data.vNodes.removeClass('sliding').closest('ul').addClass('hidden');
- },
- // event handler for toggle buttons in Hybrid(horizontal + vertical) OrgChart
- toggleVNodes: function (event) {
- var $toggleBtn = $(event.target);
- var $descWrapper = $toggleBtn.parent().next();
- var $descendants = $descWrapper.find('.node');
- var $children = $descWrapper.children().children('.node');
- if ($children.is('.sliding')) { return; }
- $toggleBtn.toggleClass('fa-plus-square fa-minus-square');
- if ($descendants.eq(0).is('.slide-up')) {
- $descWrapper.removeClass('hidden');
- this.repaint($children.get(0));
- $children.addClass('sliding').removeClass('slide-up').eq(0).one('transitionend', { 'vNodes': $children }, this.expandVNodesEnd);
- } else {
- $descendants.addClass('sliding slide-up').eq(0).one('transitionend', { 'vNodes': $descendants }, this.collapseVNodesEnd);
- $descendants.find('.toggleBtn').removeClass('fa-minus-square').addClass('fa-plus-square');
- }
- },
- //
- 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('<i class="edge verticalEdge bottomEdge fa"></i>')
- .parent().attr('colspan', 2)
- .parent().after('<tr class="lines"><td colspan="2"><div class="downLine"></div></td></tr>'
- + '<tr class="lines"><td class="rightLine"></td><td class="leftLine"></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="leftLine topLine"></td><td class="rightLine topLine"></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();
- }
- },
- //
- 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 = $('<div' + (opts.draggable ? ' draggable="true"' : '') + (data[opts.nodeId] ? ' id="' + data[opts.nodeId] + '"' : '') + (data.parentId ? ' data-parent="' + data.parentId + '"' : '') + '>')
- .addClass('node ' + (data.className || '') + (level > opts.visibleLevel ? ' slide-up' : ''));
- if (opts.nodeTemplate) {
- $nodeDiv.append(opts.nodeTemplate(data));
- } else {
- $nodeDiv.append('<div class="title">' + data[opts.nodeTitle] + '</div>')
- .append(typeof opts.nodeContent !== 'undefined' ? '<div class="content">' + (data[opts.nodeContent] || '') + '</div>' : '');
- }
- //
- 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('<i class="toggleBtn fa fa-' + icon + '-square"></i>');
- }
- } else {
- 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', 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 + 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 = $('<table>');
- $appendTo.append($nodeWrapper.append($('<tr/>').append($('<td' + (hasChildren ? ' colspan="' + childrenData.length * 2 + '"' : '') + '></td>').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 = $('<ul>');
- if (isHidden && level + 1 > opts.verticalLevel) {
- $nodesLayer.addClass(isHidden);
- }
- if (level + 1 === opts.verticalLevel) {
- $appendTo.children('table').append('<tr class="verticalNodes' + isHidden + '"><td></td></tr>')
- .find('.verticalNodes').children().append($nodesLayer);
- } else {
- $appendTo.append($nodesLayer);
- }
- } else {
- var $upperLines = $('<tr class="lines' + isHidden + '"><td colspan="' + childrenData.length * 2 + '"><div class="downLine"></div></td></tr>');
- var lowerLines = '<tr class="lines' + isHidden + '"><td class="rightLine"></td>';
- for (var i=1; i<childrenData.length; i++) {
- lowerLines += '<td class="leftLine topLine"></td><td class="rightLine topLine"></td>';
- }
- lowerLines += '<td class="leftLine"></td></tr>';
- $nodesLayer = $('<tr class="nodes' + isHidden + '">');
- 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 ? $('<li>') : $('<td colspan="2">');
- $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('<i class="edge verticalEdge bottomEdge fa"></i>');
- }
- if (!$node.find('.symbol').length) {
- $node.children('.title').prepend('<i class="fa '+ this.options.parentNodeSymbol + ' symbol"></i>');
- }
- 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 = $('<table>')
- .append($('<tr>').append($('<td colspan="2">').append(this.createNode(data))))
- .append('<tr class="lines"><td colspan="2"><div class="downLine"></div></td></tr>')
- .append('<tr class="lines"><td class="rightLine"></td><td class="leftLine"></td></tr>');
- this.$chart.prepend($table)
- .children('table:first').append('<tr class="nodes"><td colspan="2"></td></tr>')
- .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('<i class="edge verticalEdge topEdge fa"></i>');
- }
- 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 += '<td class="leftLine topLine"></td><td class="rightLine topLine"></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
- 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($('<td colspan="2">').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('<i class="edge horizontalEdge rightEdge fa"></i><i class="edge horizontalEdge leftEdge fa"></i>');
- }
- if (this.isInAction($node)) {
- this.switchHorizontalArrow($node);
- $node.children('.topEdge').removeClass('fa-chevron-up').addClass('fa-chevron-down');
- }
- },
- //
- removeNodes: function ($node) {
- var isVerticalNode = $node.parents('.verticalNodes').length > 0 ? true : false
- var $parent = isVerticalNode ? $node.parent() : $node.closest('table').parent();
- var $sibs = isVerticalNode ? $parent.siblings() : $parent.parent().siblings();
- if ($parent.is('td') || $parent.is('li')) {
- if (this.getNodeState($node, 'siblings').exist) {
- $sibs.eq(2).children('.topLine: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();
- }
- },
- exportData: function(input) {
- var that = this;
- var $chartContainer = this.$chartContainer;
- //exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;
- input = input || this.options.exportDataInput;
- 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 img;
- 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');
- img = canvas.toDataURL("image/png");
- //console.log("exportData-", img);
- console.log("exportData-over");
- input.val(img);
- }
- })
- .then(function () {
- $chartContainer.removeClass('canvasContainer');
- }, function () {
- $chartContainer.removeClass('canvasContainer');
- });
- return img;
- },
- //
- 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('<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: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('<a class="oc-download-btn' + (that.options.chartClass !== '' ? ' ' + that.options.chartClass : '') + '"'
- + ' download="' + exportFilename + '.png"></a>');
- }
- $chartContainer.find(selector).attr('href', canvas.toDataURL())[0].click();
- }
- }
- }
- })
- .then(function () {
- $chartContainer.removeClass('canvasContainer');
- }, function () {
- $chartContainer.removeClass('canvasContainer');
- });
- return true;
- }
- };
- $.fn.orgchart = function (opts) {
- return new OrgChart(this, opts).init();
- };
- }));
|