diff --git a/atms-web/src/main/webapp/Content/orgChart/jquery.orgchart.css b/atms-web/src/main/webapp/Content/orgChart/jquery.orgchart.css
index f695cd1223f66fbd2a6f8fdc376dc981168d2c46..ddd3dd0db51615e131fb38fe0496b7626087b1e8 100644
--- a/atms-web/src/main/webapp/Content/orgChart/jquery.orgchart.css
+++ b/atms-web/src/main/webapp/Content/orgChart/jquery.orgchart.css
@@ -2,22 +2,15 @@
  * jQuery OrgChart Plugin
  * https://github.com/dabeng/OrgChart
  *
- * Demos of jQuery OrgChart Plugin
- * http://dabeng.github.io/OrgChart/local-datasource/
- * http://dabeng.github.io/OrgChart/ajax-datasource/
- * http://dabeng.github.io/OrgChart/ondemand-loading-data/
- * http://dabeng.github.io/OrgChart/option-createNode/
- * http://dabeng.github.io/OrgChart/export-orgchart/
- * http://dabeng.github.io/OrgChart/integrate-map/
- *
  * Copyright 2016, dabeng
- * http://dabeng.github.io/
+ * https://github.com/dabeng
  *
  * Licensed under the MIT license:
  * http://www.opensource.org/licenses/MIT
  */
 
 .orgchart {
+  box-sizing: border-box;
   display: inline-block;
   min-height: 202px;
   min-width: 202px;
@@ -27,45 +20,29 @@
   -moz-user-select: none;
   -ms-user-select: none;
   user-select: none;
-  /*background-image: linear-gradient(90deg, rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%);*/
-  background-color:rgba(234, 237, 238, 0.39);
+  background-image: linear-gradient(90deg, rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%);
   background-size: 10px 10px;
   border: 1px dashed rgba(0,0,0,0);
   padding: 20px;
 }
 
 .orgchart .hidden, .orgchart~.hidden {
-  display: none!important;
-}
-
-.orgchart *, .orgchart *:before, .orgchart *:after {
-  -webkit-box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  box-sizing: border-box;
+  display: none;
 }
 
 .orgchart.b2t {
   transform: rotate(180deg);
-  -ms-transform: rotate(180deg);
-  -moz-transform: rotate(180deg);
-  -webkit-transform: rotate(180deg);
 }
 
 .orgchart.l2r {
   position: absolute;
   transform: rotate(-90deg) rotateY(180deg);
-  -ms-transform: rotate(-90deg) rotateY(180deg);
-  -moz-transform: rotate(-90deg) rotateY(180deg);
-  -webkit-transform: rotate(-90deg) rotateY(180deg);
   transform-origin: left top;
-  -ms-transform-origin: left top;
-  -moz-transform-origin: left top;
-  -webkit-transform-origin: left top;
 }
 
 .orgchart .verticalNodes ul {
   list-style: none;
-  margin:0px;
+  margin: 0;
   padding-left: 18px;
   text-align: left;
 }
@@ -77,6 +54,7 @@
   border: 1px solid rgba(217, 83, 79, 0.8);
 }
 .orgchart .verticalNodes>td>ul>li:first-child::before {
+  box-sizing: border-box;
   top: -4px;
   height: 30px;
   width: calc(50% - 2px);
@@ -87,6 +65,7 @@
 }
 .orgchart .verticalNodes ul>li::before,
 .orgchart .verticalNodes ul>li::after {
+  box-sizing: border-box;
   content: '';
   position: absolute;
   left: -6px;
@@ -104,24 +83,20 @@
   height: 100%;
 }
 .orgchart .verticalNodes ul>li:first-child::after {
+  box-sizing: border-box;
   top: 24px;
   width: 11px;
   border-width: 2px 0 0 2px;
 }
 .orgchart .verticalNodes ul>li:last-child::after {
+  box-sizing: border-box;
   border-width: 2px 0 0;
 }
 
 .orgchart.r2l {
   position: absolute;
   transform: rotate(90deg);
-  -ms-transform: rotate(90deg);
-  -moz-transform: rotate(90deg);
-  -webkit-transform: rotate(90deg);
   transform-origin: left top;
-  -ms-transform-origin: left top;
-  -moz-transform-origin: left top;
-  -webkit-transform-origin: left top;
 }
 
 .orgchart>.spinner {
@@ -131,8 +106,8 @@
 }
 
 .orgchart table {
-  border-spacing: 0!important;
-  border-collapse: separate!important;
+  border-spacing: 0;
+  border-collapse: separate;
 }
 
 .orgchart>table:first-child{
@@ -145,25 +120,30 @@
   padding: 0;
 }
 
-.orgchart tr.lines td.topLine {
+.orgchart .lines:nth-child(3) td {
+  box-sizing: border-box;
+  height: 20px;
+}
+
+.orgchart .lines .topLine {
   border-top: 2px solid rgba(217, 83, 79, 0.8);
 }
 
-.orgchart tr.lines td.rightLine {
+.orgchart .lines .rightLine {
   border-right: 1px solid rgba(217, 83, 79, 0.8);
   float: none;
-  border-radius: 0px;
+  border-radius: 0;
 }
 
-.orgchart tr.lines td.leftLine {
+.orgchart .lines .leftLine {
   border-left: 1px solid rgba(217, 83, 79, 0.8);
   float: none;
-  border-radius: 0px;
+  border-radius: 0;
 }
 
-.orgchart tr.lines .downLine {
+.orgchart .lines .downLine {
   background-color: rgba(217, 83, 79, 0.8);
-  margin: 0px auto;
+  margin: 0 auto;
   height: 20px;
   width: 2px;
   float: none;
@@ -171,6 +151,7 @@
 
 /* node styling */
 .orgchart .node {
+  box-sizing: border-box;
   display: inline-block;
   position: relative;
   margin: 0;
@@ -211,7 +192,7 @@
   top: -10000px;
 }
 
-.orgchart .ghost-node>* {
+.orgchart .ghost-node rect {
   fill: #ffffff;
   stroke: #bf0000;
 }
@@ -231,41 +212,23 @@
   white-space: nowrap;
   background-color: rgba(217, 83, 79, 0.8);
   color: #fff;
-  border-radius: 4px 4px 0 0; 
+  border-radius: 4px 4px 0 0;
 }
 
 .orgchart.b2t .node .title {
   transform: rotate(-180deg);
-  -ms-transform: rotate(-180deg);
-  -moz-transform: rotate(-180deg);
-  -webkit-transform: rotate(-180deg);
   transform-origin: center bottom;
-  -ms-transform-origin: center bottom;
-  -moz-transform-origin: center bottom;
-  -webkit-transform-origin: center bottom;
 }
 
 .orgchart.l2r .node .title {
   transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -ms-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -moz-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -webkit-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
   transform-origin: bottom center;
-  -ms-transform-origin: bottom center;
-  -moz-transform-origin: bottom center;
-  -webkit-transform-origin: bottom center;
   width: 120px;
 }
 
 .orgchart.r2l .node .title {
   transform: rotate(-90deg) translate(-40px, -40px);
-  -ms-transform: rotate(-90deg) translate(-40px, -40px);
-  -moz-transform: rotate(-90deg) translate(-40px, -40px);
-  -webkit-transform: rotate(-90deg) translate(-40px, -40px);
   transform-origin: bottom center;
-  -ms-transform-origin: bottom center;
-  -moz-transform-origin: bottom center;
-  -webkit-transform-origin: bottom center;
   width: 120px;
 }
 
@@ -276,6 +239,7 @@
 }
 
 .orgchart .node .content {
+  box-sizing: border-box;
   width: 100%;
   height: 20px;
   font-size: 11px;
@@ -292,36 +256,18 @@
 
 .orgchart.b2t .node .content {
   transform: rotate(180deg);
-  -ms-transform: rotate(180deg);
-  -moz-transform: rotate(180deg);
-  -webkit-transform: rotate(180deg);
   transform-origin: center top;
-  -ms-transform-origin: center top;
-  -moz-transform-origin: center top;
-  -webkit-transform-origin: center top;
 }
 
 .orgchart.l2r .node .content {
   transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -ms-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -moz-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
-  -webkit-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg);
   transform-origin: top center;
-  -ms-transform-origin: top center;
-  -moz-transform-origin: top center;
-  -webkit-transform-origin: top center;
   width: 120px;
 }
 
 .orgchart.r2l .node .content {
   transform: rotate(-90deg) translate(-40px, -40px);
-  -ms-transform: rotate(-90deg) translate(-40px, -40px);
-  -moz-transform: rotate(-90deg) translate(-40px, -40px);
-  -webkit-transform: rotate(-90deg) translate(-40px, -40px);
   transform-origin: top center;
-  -ms-transform-origin: top center;
-  -moz-transform-origin: top center;
-  -webkit-transform-origin: top center;
   width: 120px;
 }
 
@@ -331,7 +277,6 @@
   color: rgba(68, 157, 68, 0.5);
   cursor: default;
   transition: .2s;
-  -webkit-transition: .2s;
 }
 
 .orgchart.noncollapsable .node .edge {
@@ -377,8 +322,6 @@
 .orgchart .node .horizontalEdge::before {
   position: absolute;
   top: calc(50% - 7px);
-  top: -webkit-calc(50% - 7px);
-  top: -moz-calc(50% - 7px);
 }
 
 .orgchart .node .rightEdge::before {
@@ -413,39 +356,33 @@
   text-align: center;
   white-space: nowrap;
   vertical-align: middle;
-  -ms-touch-action: manipulation;
   touch-action: manipulation;
   cursor: pointer;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
   user-select: none;
   color: #fff;
-  background-color: #ca5931;
+  background-color: #5cb85c;
   border: 1px solid transparent;
-  border-color:#ca5931 ;
+  border-color: #4cae4c;
   border-radius: 4px;
 }
 
 .oc-export-btn[disabled] {
   cursor: not-allowed;
-  filter: alpha(opacity=30);
-  -webkit-box-shadow: none;
   box-shadow: none;
   opacity: 0.3;
 }
 
 .oc-export-btn:hover,.oc-export-btn:focus,.oc-export-btn:active  {
-  background-color: #ca5931;
-  border-color: #ca5931;
+  background-color: #449d44;
+  border-color: #347a34;
 }
 
 .orgchart~.mask {
   position: absolute;
-  top: 0px;
-  right: 0px;
-  bottom: 0px;
-  left: 0px;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
   z-index: 999;
   text-align: center;
   background-color: rgba(0,0,0,0.3);
@@ -460,45 +397,41 @@
 }
 
 .orgchart .node {
-  transition: all 0.3s;
-  webkit-transition: all 0.3s;
-  opacity: 1;
-  top: 0;
-  left: 0;
+  transition: transform 0.3s, opacity 0.3s;
 }
 
 .orgchart .slide-down {
   opacity: 0;
-  top: 40px;
+  transform: translateY(40px);
 }
 
 .orgchart.l2r .node.slide-down, .orgchart.r2l .node.slide-down {
-  top: 130px;
+  transform: translateY(130px);
 }
 
 .orgchart .slide-up {
   opacity: 0;
-  top: -40px;
+  transform: translateY(-40px);
 }
 
 .orgchart.l2r .node.slide-up, .orgchart.r2l .node.slide-up {
-  top: -130px;
+  transform: translateY(-130px);
 }
 
 .orgchart .slide-right {
   opacity: 0;
-  left: 130px;
+  transform: translateX(130px);
 }
 
 .orgchart.l2r .node.slide-right, .orgchart.r2l .node.slide-right {
-  left: 40px;
+  transform: translateX(40px);
 }
 
 .orgchart .slide-left {
   opacity: 0;
-  left: -130px;
+  transform: translateX(-130px);
 }
 
 .orgchart.l2r .node.slide-left, .orgchart.r2l .node.slide-left {
-  left: -40px;
+  transform: translateX(-40px);
 }
\ No newline at end of file
diff --git a/atms-web/src/main/webapp/Scripts/orgChart/jquery.orgchart.js b/atms-web/src/main/webapp/Scripts/orgChart/jquery.orgchart.js
index 87e2ae9d8a206b4ce0a60e1c0ff0e352d2270c71..332f4d072fdb5b3a5b060a33c839ed7af03ff10e 100644
--- a/atms-web/src/main/webapp/Scripts/orgChart/jquery.orgchart.js
+++ b/atms-web/src/main/webapp/Scripts/orgChart/jquery.orgchart.js
@@ -2,11 +2,8 @@
  * jQuery OrgChart Plugin
  * https://github.com/dabeng/OrgChart
  *
- * Demos of jQuery OrgChart Plugin
- * http://dabeng.github.io/OrgChart/
- *
  * Copyright 2016, dabeng
- * http://dabeng.github.io/
+ * https://github.com/dabeng
  *
  * Licensed under the MIT license:
  * http://www.opensource.org/licenses/MIT
@@ -20,12 +17,14 @@
         factory(jQuery, window, document);
     }
 }(function ($, window, document, undefined) {
-    $.fn.orgchart = function (options) {
-        var defaultOptions = {
+    var OrgChart = function (elem, opts) {
+        this.$chartContainer = $(elem);
+        this.opts = opts;
+        this.defaultOptions = {
             'nodeTitle': 'name',
             'nodeId': 'id',
             'toggleSiblingsResp': false,
-            'depth': 999,
+            'visibleLevel': 999,
             'chartClass': '',
             'exportButton': false,
             'exportFilename': 'OrgChart',
@@ -38,1381 +37,1403 @@
             'zoominLimit': 7,
             'zoomoutLimit': 0.5
         };
-
-        switch (options) {
-            case 'buildHierarchy':
-                return buildHierarchy.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'addChildren':
-                return addChildren.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'addParent':
-                return addParent.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'addSiblings':
-                return addSiblings.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'removeNodes':
-                return removeNodes.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'getHierarchy':
-                return getHierarchy.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'hideParent':
-                return hideParent.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'showParent':
-                return showParent.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'hideChildren':
-                return hideChildren.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'showChildren':
-                return showChildren.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'hideSiblings':
-                return hideSiblings.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'showSiblings':
-                return showSiblings.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'getNodeState':
-                return getNodeState.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'getRelatedNodes':
-                return getRelatedNodes.apply(this, Array.prototype.splice.call(arguments, 1));
-            case 'setChartScale':
-                return setChartScale.apply(this, Array.prototype.splice.call(arguments, 1));
-            default: // initiation time
-                var opts = $.extend(defaultOptions, options);
-        }
-
-        // build the org-chart
-        var $chartContainer = this;
-        var data = opts.data;
-        var $chart = $('<div>', {
-            'data': { 'options': opts },
-            'class': 'orgchart' + (opts.chartClass !== '' ? ' ' + opts.chartClass : '') + (opts.direction !== 't2b' ? ' ' + opts.direction : ''),
-            'click': function (event) {
-                if (!$(event.target).closest('.node').length) {
-                    $chart.find('.node.focused').removeClass('focused');
-                }
+    };
+    //
+    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();
             }
-        });
-        if ($.type(data) === 'object') {
-            if (data instanceof $) { // ul datasource
-                buildHierarchy($chart, buildJsonDS(data.children()), 0, opts);
-            } else { // local json datasource
-                buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts);
-            }
-        } else {
-            $.ajax({
-                'url': data,
-                'dataType': 'json',
-                'beforeSend': function () {
-                    $chart.append('<i class="fa fa-circle-o-notch fa-spin spinner"></i>');
+            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');
+                    }
                 }
-            })
-            .done(function (data, textStatus, jqXHR) {
-                buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts);
-            })
-            .fail(function (jqXHR, textStatus, errorThrown) {
-                console.log(errorThrown);
-            })
-            .always(function () {
-                $chart.children('.spinner').remove();
             });
-        }
-        $chartContainer.append($chart);
+            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();
+            }
 
-        // append the export button
-        if (opts.exportButton && !$chartContainer.find('.oc-export-btn').length) {
+            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' + (opts.chartClass !== '' ? ' ' + opts.chartClass : ''),
+                'class': 'oc-export-btn' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : ''),
                 'text': 'Export',
                 'click': function (e) {
                     e.preventDefault();
-                    if ($(this).children('.spinner').length) {
-                        return false;
+                    that.export();
+                }
+            });
+            this.$chartContainer.append($exportBtn);
+        },
+        setOptions: function (opts, val) {
+            if (typeof opts === 'string') {
+                if (opts === 'pan') {
+                    if (val) {
+                        this.bindPan();
+                    } else {
+                        this.unbindPan();
                     }
-                    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>');
+                }
+                if (opts === 'zoom') {
+                    if (val) {
+                        this.bindZoom();
                     } else {
-                        $mask.removeClass('hidden');
+                        this.unbindZoom();
                     }
-                    var sourceChart = $chartContainer.addClass('canvasContainer').find('.orgchart:visible').get(0);
-                    var flag = opts.direction === 'l2r' || opts.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:visible:first').css('transform', '');
-                        },
-                        'onrendered': function (canvas) {
-                            $chartContainer.find('.mask').addClass('hidden');
-                            if (opts.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(opts.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(), opts.exportFilename + '.png');
-                                }
-                                else {
-                                    $chartContainer.find('.oc-download-btn').attr('href', canvas.toDataURL())[0].click();
-                                }
-                            }
+                }
+            }
+            if (typeof opts === 'object') {
+                if (opts.data) {
+                    this.init(opts);
+                } else {
+                    if (typeof opts.pan !== 'undefined') {
+                        if (opts.pan) {
+                            this.bindPan();
+                        } else {
+                            this.unbindPan();
                         }
-                    })
-                    .then(function () {
-                        $chartContainer.removeClass('canvasContainer');
-                    }, function () {
-                        $chartContainer.removeClass('canvasContainer');
-                    });
+                    }
+                    if (typeof opts.zoom !== 'undefined') {
+                        if (opts.zoom) {
+                            this.bindZoom();
+                        } else {
+                            this.unbindZoom();
+                        }
+                    }
                 }
-            });
-            $chartContainer.append($exportBtn);
-            if (opts.exportFileextension.toLowerCase() !== 'pdf') {
-                var downloadBtn = '<a class="oc-download-btn' + (opts.chartClass !== '' ? ' ' + opts.chartClass : '') + '"'
-                  + ' download="' + opts.exportFilename + '.png"></a>';
-                $exportBtn.after(downloadBtn);
             }
-        }
 
-        if (opts.pan) {
-            $chartContainer.css('overflow', 'hidden');
-            $chart.on('mousedown touchstart', function (e) {
-                var $this = $(this);
-                if ($(e.target).closest('.node').length || (e.touches && e.touches.length > 1)) {
-                    $this.data('panning', false);
-                    return;
+            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 {
-                    $this.css('cursor', 'move').data('panning', true);
+                    lastX = parseInt(temp[12]);
+                    lastY = parseInt(temp[13]);
                 }
-                var lastX = 0;
-                var lastY = 0;
-                var lastTf = $this.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 startX = 0;
-                var startY = 0;
+                var newX = 0;
+                var newY = 0;
                 if (!e.targetTouches) { // pand on desktop
-                    startX = e.pageX - lastX;
-                    startY = e.pageY - lastY;
+                    newX = e.pageX - startX;
+                    newY = e.pageY - startY;
                 } else if (e.targetTouches.length === 1) { // pan on mobile device
-                    startX = e.targetTouches[0].pageX - lastX;
-                    startY = e.targetTouches[0].pageY - lastY;
+                    newX = e.targetTouches[0].pageX - startX;
+                    newY = e.targetTouches[0].pageY - startY;
                 } else if (e.targetTouches.length > 1) {
                     return;
                 }
-                $chart.on('mousemove touchmove', function (e) {
-                    if (!$this.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)');
                     }
-                    var lastTf = $this.css('transform');
-                    if (lastTf === 'none') {
-                        if (lastTf.indexOf('3d') === -1) {
-                            $this.css('transform', 'matrix(1, 0, 0, 1, ' + newX + ', ' + newY + ')');
-                        } else {
-                            $this.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 {
-                        var matrix = lastTf.split(',');
-                        if (lastTf.indexOf('3d') === -1) {
-                            matrix[4] = ' ' + newX;
-                            matrix[5] = ' ' + newY + ')';
-                        } else {
-                            matrix[12] = ' ' + newX;
-                            matrix[13] = ' ' + newY;
-                        }
-                        $this.css('transform', matrix.join(','));
+                        matrix[12] = ' ' + newX;
+                        matrix[13] = ' ' + newY;
                     }
-                });
+                    $chart.css('transform', matrix.join(','));
+                }
             });
-            $(document).on('mouseup touchend', function (e) {
-                if ($chart.data('panning')) {
-                    $chart.data('panning', false).css('cursor', 'default').off('mousemove');
+        },
+        //
+        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;
             });
-        }
-
-        if (opts.zoom) {
-            $chartContainer.on('wheel', function (event) {
-                event.preventDefault();
-                var newScale = 1 + (event.originalEvent.deltaY > 0 ? -0.2 : 0.2);
-                setChartScale($chart, newScale);
+            $li.children('ul').children().each(function () {
+                if (!subObj.children) {
+                    subObj.children = [];
+                }
+                subObj.children.push(that.buildJsonDS($(this)));
             });
-
-            $chartContainer.on('touchstart', function (e) {
-                if (e.touches && e.touches.length === 2) {
-                    $chart.data('pinching', true);
-                    var dist = getPinchDist(e);
-                    $chart.data('pinchDistStart', dist);
+            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)));
             });
-            $(document).on('touchmove', function (e) {
-                if ($chart.data('pinching')) {
-                    var dist = getPinchDist(e);
-                    $chart.data('pinchDistEnd', dist);
+            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!';
+                    }
                 }
-            })
-            .on('touchend', function (e) {
-                if ($chart.data('pinching')) {
-                    $chart.data('pinching', false);
-                    var diff = $chart.data('pinchDistEnd') - $chart.data('pinchDistStart');
-                    if (diff > 0) {
-                        setChartScale($chart, 1.2);
-                    } else if (diff < 0) {
-                        setChartScale($chart, 0.8);
+            }
+            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};
                 }
-            });
-        }
-
-        return $chartContainer;
-    };
-
-    function getPinchDist(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));
-    }
-
-    function setChartScale($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 = window.parseFloat(matrix[3]) * newScale;
-                if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) {
-                    $chart.css('transform', lastTf + ' scale(' + newScale + ',' + newScale + ')');
+            } 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 {
-                targetScale = window.parseFloat(matrix[1]) * newScale;
-                if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) {
-                    $chart.css('transform', lastTf + ' scale3d(' + newScale + ',' + newScale + ', 1)');
+                $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};
                 }
             }
-        }
-    }
-
-    function buildJsonDS($li) {
-        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)
-        };
-        if ($li[0].id) {
-            subObj.id = $li[0].id;
-        }
-        $li.children('ul').children().each(function () {
-            if (!subObj.children) { subObj.children = []; }
-            subObj.children.push(buildJsonDS($(this)));
-        });
-        return subObj;
-    }
-
-    function attachRel(data, flags) {
-        data.relationship = flags + (data.children && data.children.length > 0 ? 1 : 0);
-        if (data.children) {
-            data.children.forEach(function (item) {
-                attachRel(item, '1' + (data.children.length > 1 ? 1 : 0));
-            });
-        }
-        return data;
-    }
-
-    function loopChart($chart) {
-        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(loopChart($(this)));
-        });
-        return subObj;
-    }
-
-    function getHierarchy($chart) {
-        var $chart = $chart || $(this).find('.orgchart');
-        if (!$chart.find('.node:first')[0].id) {
-            return 'Error: Nodes of orghcart to be exported must have id attribute!';
-        }
-        return loopChart($chart);
-    }
-
-    // detect the exist/display state of related node
-    function getNodeState($node, relation) {
-        var $target = {};
-        if (relation === 'parent') {
-            $target = $node.closest('.nodes').siblings(':first');
-        } else if (relation === 'children') {
-            $target = $node.closest('tr').siblings();
-        } else if (relation === 'siblings') {
-            $target = $node.closest('table').parent().siblings();
-        }
-        if ($target.length) {
-            if ($target.is(':visible')) {
-                return { 'exist': true, 'visible': true };
+            return {'exist': false, 'visible': false};
+        },
+        // find the related nodes
+        getRelatedNodes: function ($node, relation) {
+            if (!$node || !($node instanceof $) || !$node.is('.node')) {
+                return $();
             }
-            return { 'exist': true, 'visible': false };
-        }
-        return { 'exist': false, 'visible': false };
-    }
-
-    // find the related nodes
-    function getRelatedNodes($node, relation) {
-        if (relation === 'parent') {
-            return $node.closest('.nodes').parent().children(':first').find('.node');
-        } else if (relation === 'children') {
-            return $node.closest('table').children(':last').children().find('.node:first');
-        } else if (relation === 'siblings') {
-            return $node.closest('table').parent().siblings().find('.node:first');
-        }
-    }
-
-    // recursively hide the ancestor node and sibling nodes of the specified node
-    function hideParent($node) {
-        var $temp = $node.closest('table').closest('tr').siblings();
-        if ($temp.eq(0).find('.spinner').length) {
-            $node.closest('.orgchart').data('inAjax', false);
-        }
-        // hide the sibling nodes
-        if (getNodeState($node, 'siblings').visible) {
-            hideSiblings($node);
-        }
-        // hide the lines
-        var $lines = $temp.slice(1);
-        $lines.css('visibility', 'hidden');
-        // hide the superior nodes with transition
-        var $parent = $temp.eq(0).find('.node');
-        var grandfatherVisible = getNodeState($parent, 'parent').visible;
-        if ($parent.length && $parent.is(':visible')) {
-            $parent.addClass('slide slide-down').one('transitionend', function () {
-                $parent.removeClass('slide');
-                $lines.removeAttr('style');
-                $temp.addClass('hidden');
-            });
-        }
-        // if the current node has the parent node, hide it recursively
-        if ($parent.length && grandfatherVisible) {
-            hideParent($parent);
-        }
-    }
-
-    // show the parent node of the specified node
-    function showParent($node) {
-        // just show only one superior level
-        var $temp = $node.closest('table').closest('tr').siblings().removeClass('hidden');
-        // just show only one line
-        $temp.eq(2).children().slice(1, -1).addClass('hidden');
-        // show parent node with animation
-        var parent = $temp.eq(0).find('.node')[0];
-        repaint(parent);
-        $(parent).addClass('slide').removeClass('slide-down').one('transitionend', function () {
-            $(parent).removeClass('slide');
-            if (isInAction($node)) {
-                switchVerticalArrow($node.children('.topEdge'));
-            }
-        });
-    }
-
-    // recursively hide the descendant nodes of the specified node
-    function hideChildren($node) {
-        var $temp = $node.closest('tr').siblings();
-        if ($temp.last().find('.spinner').length) {
-            $node.closest('.orgchart').data('inAjax', false);
-        }
-        var $visibleNodes = $temp.last().find('.node:visible');
-        var isVerticalDesc = $temp.last().is('.verticalNodes') ? true : false;
-        if (!isVerticalDesc) {
-            var $lines = $visibleNodes.closest('table').closest('tr').prevAll('.lines').css('visibility', 'hidden');
-        }
-        $visibleNodes.addClass('slide slide-up').eq(0).one('transitionend', function () {
-            $visibleNodes.removeClass('slide');
-            if (isVerticalDesc) {
-                $temp.addClass('hidden');
+            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 {
-                $lines.removeAttr('style').addClass('hidden').siblings('.nodes').addClass('hidden');
-                $temp.last().find('.verticalNodes').addClass('hidden');
+                return $();
             }
-            if (isInAction($node)) {
-                switchVerticalArrow($node.children('.bottomEdge'));
+        },
+        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);
             }
-        });
-    }
-
-    // show the children nodes of the specified node
-    function showChildren($node) {
-        var $levels = $node.closest('tr').siblings();
-        var isVerticalDesc = $levels.is('.verticalNodes') ? true : false;
-        var $descendants = isVerticalDesc
-          ? $levels.removeClass('hidden').find('.node:visible')
-          : $levels.removeClass('hidden').eq(2).children().find('.node:first');
-        // the two following statements are used to enforce browser to repaint
-        repaint($descendants.get(0));
-        $descendants.addClass('slide').removeClass('slide-up').eq(0).one('transitionend', function () {
-            $descendants.removeClass('slide');
-            if (isInAction($node)) {
-                switchVerticalArrow($node.children('.bottomEdge'));
-            }
-        });
-    }
-
-    // hide the sibling nodes of the specified node
-    function hideSiblings($node, direction) {
-        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:visible').addClass('slide slide-right');
+            // 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 {
-                $nodeContainer.nextAll().find('.node:visible').addClass('slide slide-left');
+                event.data.animatedNodes.closest('.nodes').prevAll('.lines').removeAttr('style').addBack().addClass('hidden');
+                event.data.lowerLevel.last().find('.verticalNodes').addClass('hidden');
             }
-        } else {
-            $nodeContainer.prevAll().find('.node:visible').addClass('slide slide-right');
-            $nodeContainer.nextAll().find('.node:visible').addClass('slide slide-left');
-        }
-        var $animatedNodes = $nodeContainer.siblings().find('.slide');
-        var $lines = $animatedNodes.closest('.nodes').prevAll('.lines').css('visibility', 'hidden');
-        $animatedNodes.eq(0).one('transitionend', function () {
-            $lines.removeAttr('style');
+            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');
-            $animatedNodes.removeClass('slide');
-            $siblings.find('.node:visible:gt(0)').removeClass('slide-left slide-right').addClass('slide-up')
-              .end().find('.lines, .nodes, .verticalNodes').addClass('hidden')
-              .end().addClass('hidden');
-
-            if (isInAction($node)) {
-                switchHorizontalArrow($node);
+                .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);
             }
-        });
-    }
-
-    // show the sibling nodes of the specified node
-    function showSiblings($node, direction) {
-        // firstly, show the sibling td tags
-        var $siblings = $();
-        if (direction) {
-            if (direction === 'left') {
-                $siblings = $node.closest('table').parent().prevAll().removeClass('hidden');
+        },
+        // 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 {
-                $siblings = $node.closest('table').parent().nextAll().removeClass('hidden');
+                $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');
             }
-        } 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 (!getNodeState($node, 'parent').visible) {
-            $upperLevel.removeClass('hidden');
-            var parent = $upperLevel.find('.node')[0];
-            repaint(parent);
-            $(parent).addClass('slide').removeClass('slide-down').one('transitionend', function () {
-                $(this).removeClass('slide');
-            });
-        }
-        // lastly, show the sibling nodes with animation
-        $siblings.find('.node:visible').addClass('slide').removeClass('slide-left slide-right').eq(-1).one('transitionend', function () {
-            $siblings.find('.node:visible').removeClass('slide');
-            if (isInAction($node)) {
-                switchHorizontalArrow($node);
+            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');
             }
-        });
-    }
-
-    // start up loading status for requesting new nodes
-    function startLoading($arrow, $node, options) {
-        var $chart = $node.closest('.orgchart');
-        if (typeof $chart.data('inAjax') !== 'undefined' && $chart.data('inAjax') === true) {
-            return false;
-        }
-
-        $arrow.addClass('hidden');
-        $node.append('<i class="fa fa-circle-o-notch fa-spin spinner"></i>');
-        $node.children().not('.spinner').css('opacity', 0.2);
-        $chart.data('inAjax', true);
-        $('.oc-export-btn' + (options.chartClass !== '' ? '.' + options.chartClass : '')).prop('disabled', true);
-        return true;
-    }
-
-    // terminate loading status for requesting new nodes
-    function endLoading($arrow, $node, options) {
-        var $chart = $node.closest('div.orgchart');
-        $arrow.removeClass('hidden');
-        $node.find('.spinner').remove();
-        $node.children().removeAttr('style');
-        $chart.data('inAjax', false);
-        $('.oc-export-btn' + (options.chartClass !== '' ? '.' + options.chartClass : '')).prop('disabled', false);
-    }
-
-    // whether the cursor is hovering over the node
-    function isInAction($node) {
-        return $node.children('.edge').attr('class').indexOf('fa-') > -1 ? true : false;
-    }
-
-    function switchVerticalArrow($arrow) {
-        $arrow.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down');
-    }
-
-    function switchHorizontalArrow($node) {
-        var opts = $node.closest('.orgchart').data('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');
+        },
+        //
+        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 {
-                    $node.children('.leftEdge').addClass('fa-chevron-right').removeClass('fa-chevron-left');
+                    $siblings = $node.closest('table').parent().nextAll().removeClass('hidden');
                 }
+            } else {
+                $siblings = $node.closest('table').parent().siblings().removeClass('hidden');
             }
-            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');
-                }
+            // 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');
             }
-        } 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);
-        }
-    }
-
-    function repaint(node) {
-        if (node) {
-            node.style.offsetWidth = node.offsetWidth;
-        }
-    }
-
-
-    var expandedOrgList = [];
-
-    // create node
-    function createNode(nodeData, level, opts) {
-
-        $.each(nodeData.children, function (index, child) {
-            child.parentId = nodeData.id;
-        });
-        var dtd = $.Deferred();
-        // construct the content of node
-        var $nodeDiv = $('<div' + (opts.draggable ? ' draggable="true"' : '') + (nodeData[opts.nodeId] ? ' id="' + nodeData[opts.nodeId] + '"' : '') + (nodeData.parentId ? ' data-parent="' + nodeData.parentId + '"' : '') + '>')
-          .addClass('node ' + (nodeData.className || '') + (level >= opts.depth ? ' slide-up' : ''))
-          .append('<div class="title">' + nodeData[opts.nodeTitle] + '</div>')
-          .append(typeof opts.nodeContent !== 'undefined' ? '<div class="content">' + (nodeData[opts.nodeContent] || '') + '</div>' : '');
-        // append 4 direction arrows or expand/collapse buttons
-        var flags = nodeData.relationship || '';
-        if (opts.verticalDepth && (level + 2) > opts.verticalDepth) {
-            if ((level + 1) >= opts.verticalDepth && Number(flags.substr(2, 1))) {
-                var icon = level + 1 >= opts.depth ? '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>');
+            // 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;
             }
-        }
 
-        $nodeDiv.on('mouseenter mouseleave', function (event) {
-            var $node = $(this), flag = 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 = getNodeState($node, 'parent').visible;
+                    flag = this.getNodeState($node, 'parent').visible;
                     $topEdge.toggleClass('fa-chevron-up', !flag).toggleClass('fa-chevron-down', flag);
                 }
                 if ($bottomEdge.length) {
-                    flag = getNodeState($node, 'children').visible;
+                    flag = this.getNodeState($node, 'children').visible;
                     $bottomEdge.toggleClass('fa-chevron-down', !flag).toggleClass('fa-chevron-up', flag);
                 }
                 if ($leftEdge.length) {
-                    switchHorizontalArrow($node);
+                    this.switchHorizontalArrow($node);
                 }
             } else {
                 $node.children('.edge').removeClass('fa-chevron-up fa-chevron-down fa-chevron-right fa-chevron-left');
             }
-        });
-
-        // define click event handler
-        $nodeDiv.on('click', function (event) {
-            $(this).closest('.orgchart').find('.focused').removeClass('focused');
-            $(this).addClass('focused');
-        });
-
-        // define click event handler for the top edge
-        $nodeDiv.on('click', '.topEdge', function (event) {
+        },
+        //
+        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 $node = $that.parent();
-            var parentState = getNodeState($node, 'parent');
+            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('.slide')) { return; }
+                if ($parent.is('.sliding')) {
+                    return;
+                }
                 // hide the ancestor nodes and sibling nodes of the specified node
                 if (parentState.visible) {
-                    hideParent($node);
-                    $parent.one('transitionend', function () {
-                        if (isInAction($node)) {
-                            switchVerticalArrow($that);
-                            switchHorizontalArrow($node);
-                        }
-                    });
+                    this.hideParent($node);
+                    $parent.one('transitionend', {'topEdge': $topEdge}, this.HideFirstParentEnd.bind(this));
                 } else { // show the ancestors and siblings
-                    showParent($node);
+                    this.showParent($node);
                 }
-            } else {
-                // load the new parent node of the specified node by ajax request
-                var nodeId = $that.parent()[0].id;
+            } else { // load the new parent node of the specified node by ajax request
                 // start up loading status
-                if (startLoading($that, $node, opts)) {
-                    // load new nodes 
-                    $.ajax({ 'url': $.isFunction(opts.ajaxURL.parent) ? opts.ajaxURL.parent(nodeData) : opts.ajaxURL.parent + nodeId, 'dataType': 'json' })
-                    .done(function (data) {
-                        if ($node.closest('.orgchart').data('inAjax')) {
-                            if (!$.isEmptyObject(data)) {
-                                addParent.call($node.closest('.orgchart').parent(), $node, data, opts);
-                            }
-                        }
-                    })
-                    .fail(function () { console.log('Failed to get parent node data'); })
-                    .always(function () { endLoading($that, $node, opts); });
+                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);
                 }
             }
-        });
-
-        //get webapi token info
-        function getApiToken() {
-            var apiTokenObj = JSON.parse(getCookie('AtmsApiToken'));
-            var apiToken = apiTokenObj.access_token;
-            var tokenType = apiTokenObj.token_type;
-            var api_host = apiTokenObj.api_host;
-
-            return {
-                apiToken: apiToken,
-                tokenType: tokenType,
-                api_host: api_host
-            };
-        };
-
-        function getParameterByName(name, url) {
-            if (!url) url = window.location.href;
-            name = name.replace(/[\[\]]/g, "\\$&");
-            var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
-                results = regex.exec(url);
-            if (!results) return null;
-            if (!results[2]) return '';
-            return decodeURIComponent(results[2].replace(/\+/g, " "));
-        };
-
-        // bind click event handler for the bottom edge - jacob0702
-        $nodeDiv.on('click', '.bottomEdge', function (event) {
+        },
+        //
+        bottomEdgeClickHandler: function (event) {
             event.stopPropagation();
-            var $that = $(this);
-            var $node = $that.parent();
-            var childrenState = getNodeState($node, 'children');
-
-            var plevel = $node.find('.title:eq(0)').attr('plevel');
-
+            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('.node:visible').is('.slide')) {
+                if ($children.find('.sliding').length) {
                     return;
                 }
-
-                var parentOrgID = $node.find('#OrgID').text()
                 // hide the descendant nodes of the specified node
                 if (childrenState.visible) {
-
-                    hideChildren($node);
-
-                    expandedOrgList = jQuery.grep(expandedOrgList, function (value) {
-                        return value != parentOrgID;
-                    });
+                    this.hideChildren($node);
                 } else { // show the descendants
-
-                    if (expandedOrgList.indexOf(parentOrgID) < 0) {
-                        expandedOrgList.push(parentOrgID);
-                    }
-
-                    showChildren($node);
+                    this.showChildren($node);
                 }
             } else { // load the new children nodes of the specified node by ajax request
-                var nodeId = $that.parent()[0].id;
-                if (startLoading($that, $node, opts)) {
-
-                    var apiTokenObj = getApiToken();
-                    var antiForgeryToken = $('input[name="__RequestVerificationToken"]').val();
-                    var url = $.isFunction(opts.ajaxURL.children) ? opts.ajaxURL.children(nodeData) : opts.ajaxURL.children + nodeId;
-
-                    $.ajax({
-                        'type': "get",
-                        'url': url,
-                        'dataType': 'json',
-                        beforeSend: function (xhr) {
-                            console.log("__RequestVerificationToken", antiForgeryToken);
-                            xhr.setRequestHeader('Authorization', apiTokenObj.tokenType + ' ' + apiTokenObj.apiToken);
-                        }
-                    })
-                        .done(function (data, textStatus, jqXHR) {
-
-                            var parentOrgID = getParameterByName('parentOrgID', url);
-                            if (expandedOrgList.indexOf(parentOrgID) < 0) {
-                                expandedOrgList.push(parentOrgID);
-                            }
-
-                            if ($node.closest('.orgchart').data('inAjax')) {
-
-                                //modified by jacob on May 30
-                                if (data.length == 0) return;
-
-                                data = {
-                                    'children': createNodes(data, plevel)
-                                };
-                                if (data.children.length) {
-                                    addChildren($node, data, $.extend({
-                                    }, opts, {
-                                        depth: 0
-                                    }));
-                                }
-                            }
-                        })
-                        .fail(function (jqXHR, textStatus, errorThrown) {
-                            console.log('Failed to get children nodes data');
-                        })
-                        .always(function () {
-                            endLoading($that, $node, opts);
-                        });
+                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);
                 }
             }
-        });
-
-
-        function getCookie(cname) {
-            var name = cname + "=";
-            var ca = document.cookie.split(';');
-            for (var i = 0; i < ca.length; i++) {
-                var c = ca[i].trim();
-                //if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
-                if (c.indexOf(name) == 0) return ATMS_EXTRA.decodeCookieValue(c.substring(name.length, c.length));
-            }
-            return "";
-        }
-
-        //added by jacob on May 30
-        var createNodes = function (data, plevel) {
-            var plevel = plevel + 1;
-
-            var childrenNode = [];
-            if (data !== undefined && data !== null && data.length > 0) {
-                data.forEach(function (row) {
-
-                    var newNode = createNewNode(row);
-
-                    newNode.pLevel = plevel;
-                    childrenNode.push(newNode);
-                });
-
-                return childrenNode;
-            }
-        };
-
-        var ReplaceObj = {
-            //顶部节点
-            topNodeName: '{{#topNodeName#}}',
-            //事业部名称
-            dimensionValue: '{{#dimensionValue#}}',
-            dimensionValueID: '{{#dimensionValueID#}}',
-            //高亮部分统计
-            //hightNodeCount: '{{#dimensionValueCount#}}', //比如 '100'
-            //hightNodeName: '{{#attributeName#}}',   //比如'分公司'
-            //普通维度统计
-            attributeID: '{{#attributeID#}}',
-            attributeName: '{{#attributeName#}}',  //维度名字,如用户数,覆盖区域,所属行业等
-            attributeValueCount: '{{#attributeValueCount#}}',      //对于维度的统计
-            attributeType: '{{#attributeType#}}',             //维度的类型区分值,如1:事业部,2:分公司
-            firstAttrName: '{{#firstAttr}}',
-            secondAttrName: '{{#secondAttr}}',
-            firstAttrTitle: '{{#firstAttrTitle}}',
-            secondAttrTitle: '{{#secondAttrTitle}}'
-        };
-
-        var filterString = function (str, maxLength) {
-            if (str != '' && str != undefined && str != null) {
-                var apostrophe = '...';
-                var twoChar = 'mm';
-                var r = /[^\x00-\xff]/g;
-                if (str.replace(r, twoChar).length <= maxLength) {
-                    return str;
-                }
-                var m = Math.floor(maxLength / 2);
-                for (var i = m; i < str.length; i++) {
-                    if (str.substr(0, i).replace(r, twoChar).length >= maxLength) {
-                        return str.substr(0, i) + apostrophe;
-                    }
-                }
-                return str;
-            }
-        };
-
-        var template = {
-            topNodeTemplate: $("#dimension-topNode").html(),  //最顶层节点
-            nameNodeQuery: $("#dimension-org-node-name-query").html(),  //机构名称 
-            nameNodeEdit: $("#dimension-org-node-name-edit").html(),  //机构名称 
-            rowNode: $("#dimension-row-node").html() //普通行
-        };
-
-        //创建orgchart节点
-        var createNewNode = function (row) {
-
-            var temp = [];
-            var relationship = '110';
-            if (row.hasChildren !== undefined && row.hasChildren) {
-                relationship = '111';
-            }
-
-            var templateNameNode = template.nameNodeQuery;
-            if (opts.hasEditPermission) {
-                templateNameNode = template.nameNodeEdit;
-            }
-
-            var node = {
-                'id': row.dimensionValueID,
-                'name': templateNameNode.replaceToEnd(ReplaceObj.dimensionValue, row.dimensionValue)
-                    .replaceToEnd(ReplaceObj.dimensionValueID, row.dimensionValueID)
-                    .replaceToEnd(ReplaceObj.dimensionValueID, row.dimensionValueID)
-                    .replaceToEnd(ReplaceObj.firstAttrName, row.firstAttrName === null ? ' 未指定 ' : filterString(row.firstAttrName, 10))
-                    .replaceToEnd(ReplaceObj.secondAttrName, row.secondAttrName === null ? ' 未指定 ' : filterString(row.secondAttrName, 10))
-                    .replaceToEnd(ReplaceObj.firstAttrTitle, row.firstAttrName === null ? ' 未指定 ' : row.firstAttrName)
-                    .replaceToEnd(ReplaceObj.secondAttrTitle, row.secondAttrName === null ? ' 未指定 ' : row.secondAttrName),
-
-                'title': (function () {
-
-                    row.orgDtoList.forEach(function (item, index) {
-
-                        temp += template.rowNode.replaceToEnd(ReplaceObj.attributeName, item.attributeName)
-                                .replaceToEnd(ReplaceObj.attributeValueCount, item.attributeValueCount)
-                                .replaceToEnd(ReplaceObj.dimensionValueID, row.dimensionValueID)
-                                .replaceToEnd(ReplaceObj.dimensionValue, row.dimensionValue)
-                               .replaceToEnd(ReplaceObj.attributeID, item.attributeID)
-                               .replaceToEnd(ReplaceObj.attributeType, item.attributeType);
-                    });
-                    return temp;
-
-                })(),
-                'children': [],
-                'relationship': relationship
-            };
-
-            //特殊处理,如果没有子机构,只显示本身即可
-            if (row.hasChildren === undefined || !row.hasChildren) {
-                node.title = '';
-            }
-
-            return node;
-        };
-
-        // event handler for toggle buttons in Hybrid(horizontal + vertical) OrgChart
-        $nodeDiv.on('click', '.toggleBtn', function (event) {
-            var $this = $(this);
-            var $descWrapper = $this.parent().next();
-            var $descendants = $descWrapper.find('.node');
-            var $children = $descWrapper.children().children('.node');
-            if ($children.is('.slide')) {
-                return;
-            }
-            $this.toggleClass('fa-plus-square fa-minus-square');
-            if ($descendants.eq(0).is('.slide-up')) {
-                $descWrapper.removeClass('hidden');
-                repaint($children.get(0));
-                $children.addClass('slide').removeClass('slide-up').eq(0).one('transitionend', function () {
-                    $children.removeClass('slide');
-                });
-            } else {
-                $descendants.addClass('slide slide-up').eq(0).one('transitionend', function () {
-                    $descendants.removeClass('slide');
-                    // $descWrapper.addClass('hidden');
-                    $descendants.closest('ul').addClass('hidden');
-                });
-                $descendants.find('.toggleBtn').removeClass('fa-minus-square').addClass('fa-plus-square');
-            }
-        });
-
-        // bind click event handler for the left and right edges
-        $nodeDiv.on('click', '.leftEdge, .rightEdge', function (event) {
+        },
+        //
+        hEdgeClickHandler: function (event) {
             event.stopPropagation();
-            var $that = $(this);
-            var $node = $that.parent();
-            var siblingsState = getNodeState($node, 'siblings');
+            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('.node:visible').is('.slide')) {
+                if ($siblings.find('.sliding').length) {
                     return;
                 }
                 if (opts.toggleSiblingsResp) {
                     var $prevSib = $node.closest('table').parent().prev();
                     var $nextSib = $node.closest('table').parent().next();
-                    if ($that.is('.leftEdge')) {
+                    if ($hEdge.is('.leftEdge')) {
                         if ($prevSib.is('.hidden')) {
-                            showSiblings($node, 'left');
+                            this.showSiblings($node, 'left');
                         } else {
-                            hideSiblings($node, 'left');
+                            this.hideSiblings($node, 'left');
                         }
                     } else {
                         if ($nextSib.is('.hidden')) {
-                            showSiblings($node, 'right');
+                            this.showSiblings($node, 'right');
                         } else {
-                            hideSiblings($node, 'right');
+                            this.hideSiblings($node, 'right');
                         }
                     }
                 } else {
                     if (siblingsState.visible) {
-                        hideSiblings($node);
+                        this.hideSiblings($node);
                     } else {
-                        showSiblings($node);
+                        this.showSiblings($node);
                     }
                 }
             } else {
                 // load the new sibling nodes of the specified node by ajax request
-                var nodeId = $that.parent()[0].id;
-                var url = (getNodeState($node, 'parent').exist) ?
-                  ($.isFunction(opts.ajaxURL.siblings) ? opts.ajaxURL.siblings(nodeData) : opts.ajaxURL.siblings + nodeId) :
-                  ($.isFunction(opts.ajaxURL.families) ? opts.ajaxURL.families(nodeData) : opts.ajaxURL.families + nodeId);
-                if (startLoading($that, $node, opts)) {
-                    $.ajax({
-                        'url': url, 'dataType': 'json'
-                    })
-                    .done(function (data, textStatus, jqXHR) {
-                        if ($node.closest('.orgchart').data('inAjax')) {
-                            if (data.siblings || data.children) {
-                                addSiblings($node, data, opts);
-                            }
-                        }
-                    })
-                    .fail(function (jqXHR, textStatus, errorThrown) {
-                        console.log('Failed to get sibling nodes data');
-                    })
-                    .always(function () {
-                        endLoading($that, $node, opts);
-                    });
+                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);
                 }
             }
-        });
-        if (opts.draggable) {
-            $nodeDiv.on('dragstart', function (event) {
-                var origEvent = event.originalEvent;
-                var isFirefox = /firefox/.test(window.navigator.userAgent.toLowerCase());
-                if (isFirefox) {
-                    origEvent.dataTransfer.setData('text/html', 'hack for firefox');
-                }
-                // if users enable zoom or direction options
-                if ($nodeDiv.closest('.orgchart').css('transform') !== 'none') {
-                    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 scale = Math.abs(window.parseFloat((opts.direction === 't2b' || opts.direction === 'b2t') ? transValues[0].slice(transValues[0].indexOf('(') + 1) : transValues[1]));
-                    ghostNode.setAttribute('width', $nodeDiv.outerWidth(false));
-                    ghostNode.setAttribute('height', $nodeDiv.outerHeight(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);
+        },
+        //
+        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 {
-                        origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset);
+                        $(node).addClass('allowedDrop');
                     }
                 }
-                var $dragged = $(this);
-                var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first');
-                var $dragHier = $dragged.closest('table').find('.node');
-                $dragged.closest('.orgchart')
-                  .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');
-                          }
-                      }
-                  });
-            })
-                  .on('dragover', function (event) {
-                      event.preventDefault();
-                      if (!$(this).is('.allowedDrop')) {
-                          event.originalEvent.dataTransfer.dropEffect = 'none';
-                      }
-                  })
-            .on('dragend', function (event) {
-                $(this).closest('.orgchart').find('.allowedDrop').removeClass('allowedDrop');
-            })
-            .on('drop', function (event) {
-                var $dropZone = $(this);
-
-                var $orgchart = $dropZone.closest('.orgchart');
-                var $dragged = $orgchart.data('dragged');
-                $orgchart.find('.allowedDrop').removeClass('allowedDrop');
-
-                //jacob - rewrite to meeting our business requirements, it happened when drag and ajax loading data together
-                var destOrgID = $dropZone.find('#OrgID').text();
-                var srcOrgID = $dragged.find('#OrgID').text();
-
-                //use ajax to request webapi to update database
-                var apiTokenObj = getApiToken();
-                var webApiUrl = apiTokenObj.api_host + constant.webapi.prefix + '/org/updateHierarchy?srcOrgID=' + srcOrgID + '&destOrgID=' + destOrgID;
-
-                $.ajax({
-                    'url': webApiUrl,
-                    'dataType': 'json',
-                    beforeSend: function (xhr) {
-                        console.log("Authorization", apiTokenObj.tokenType + ' ' + apiTokenObj.apiToken);
-                        xhr.setRequestHeader("Authorization", apiTokenObj.tokenType + ' ' + apiTokenObj.apiToken);
-                    }
-                })
-                .done(function (data, textStatus, jqXHR) {
-
-                    //trigger the children loading of this node
-                    //$dropZone.find('.bottomEdge').click();
-
-                    //we encountered many bugs when using above trigger to load children,
-                    //then we changed the following way
-
-                    expandedOrgList.push(destOrgID);
-                    opts.initOrgChartData('', expandedOrgList);
-
-                    //var $dragZone = $dragged.closest('.nodes').siblings().eq(0).children();
-                    //// firstly, deal with the hierarchy of drop zone
-                    //if (!$dropZone.closest('tr').siblings().length) { // if the drop zone is a leaf node
-                    //    //remove dragged node
-                    //    $('#subsidiary-chart').orgchart('removeNodes', $dragged);
-                    //} 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">&nbsp;</td><td class="rightLine topLine">&nbsp;</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();
-                    //}
-                    //$orgchart.triggerHandler({
-                    //    'type': 'nodedropped.orgchart', 'draggedNode': $dragged, 'dragZone': $dragZone.children(), 'dropZone': $dropZone
-                    //});
-                })
-                .fail(function (jqXHR, textStatus, errorThrown) {
-                    console.log('Failed to update dragged hierarchy');
-                })
             });
-        }
-        // allow user to append dom modification after finishing node create of orgchart
-        if (opts.createNode) {
-            opts.createNode($nodeDiv, nodeData);
-        }
-        dtd.resolve($nodeDiv);
-        return dtd.promise();
-    }
-    // recursively build the tree
-    function buildHierarchy($appendTo, nodeData, level, opts, callback) {
-        var $nodeWrapper;
-        // Construct the node
-        var $childNodes = nodeData.children;
-        var hasChildren = $childNodes ? $childNodes.length : false;
-        var isVerticalNode = (opts.verticalDepth && (level + 1) >= opts.verticalDepth) ? true : false;
-        if (Object.keys(nodeData).length > 1) { // if nodeData has nested structure
-            $nodeWrapper = isVerticalNode ? $appendTo : $('<table>');
-            if (!isVerticalNode) {
-                $appendTo.append($nodeWrapper);
-            }
-            $.when(createNode(nodeData, level, opts))
-            .done(function ($nodeDiv) {
-                if (isVerticalNode) {
-                    $nodeWrapper.append($nodeDiv);
-                } else {
-                    $nodeWrapper.append($nodeDiv.wrap('<tr><td' + (hasChildren ? ' colspan="' + $childNodes.length * 2 + '"' : '') + '></td></tr>').closest('tr'));
+        },
+        //
+        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);
                 }
-                if (callback) {
-                    callback();
+                $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);
                 }
-            })
-            .fail(function () {
-                console.log('Failed to creat node')
-            });
-        }
-        // Construct the inferior nodes and connectiong lines
-        if (hasChildren) {
-            if (Object.keys(nodeData).length === 1) { // if nodeData is just an array
-                $nodeWrapper = $appendTo;
             }
-            var isHidden = (level + 1 >= opts.depth || nodeData.collapsed) ? ' hidden' : '';
-            var isVerticalLayer = (opts.verticalDepth && (level + 2) >= opts.verticalDepth) ? true : false;
-
-            // draw the line close to parent node
-            if (!isVerticalLayer) {
-                $nodeWrapper.append('<tr class="lines' + isHidden + '"><td colspan="' + $childNodes.length * 2 + '"><div class="downLine"></div></td></tr>');
-            }
-            // draw the lines close to children nodes
-            var lineLayer = '<tr class="lines' + isHidden + '"><td class="rightLine">&nbsp;</td>';
-            for (var i = 1; i < $childNodes.length; i++) {
-                lineLayer += '<td class="leftLine topLine">&nbsp;</td><td class="rightLine topLine">&nbsp;</td>';
-            }
-            lineLayer += '<td class="leftLine">&nbsp;</td></tr>';
-            var $nodeLayer;
-            if (isVerticalLayer) {
-                $nodeLayer = $('<ul>');
-                if (isHidden) {
-                    $nodeLayer.addClass(isHidden);
+            // 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();
                 }
-                if (level + 2 === opts.verticalDepth) {
-                    $nodeWrapper.append('<tr class="verticalNodes' + isHidden + '"><td></td></tr>')
-                      .find('.verticalNodes').children().append($nodeLayer);
+            } 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 {
-                    $nodeWrapper.append($nodeLayer);
+                    console.log("orgChart: touchmove 3: this node (" + touchingNodeElement.id + ") is NOT allowed to be a drop target");
+                    this.touchTargetNode = null;
                 }
             } else {
-                $nodeLayer = $('<tr class="nodes' + isHidden + '">');
-                $nodeWrapper.append(lineLayer).append($nodeLayer);
-            }
-            // recurse through children nodes
-            $.each($childNodes, function () {
-                var $nodeCell = isVerticalLayer ? $('<li>') : $('<td colspan="2">');
-                $nodeLayer.append($nodeCell);
-                buildHierarchy($nodeCell, this, level + 1, opts, callback);
-            });
-        }
-    }
-
-    // build the child nodes of specific node
-    function buildChildNode($appendTo, nodeData, opts, callback) {
-        var opts = opts || $appendTo.closest('.orgchart').data('options');
-        var data = nodeData.children || nodeData.siblings;
-        $appendTo.find('td:first').attr('colspan', data.length * 2);
-        buildHierarchy($appendTo, { 'children': data }, 0, opts, callback);
-    }
-    // exposed method
-    function addChildren($node, data, opts) {
-        var opts = opts || $node.closest('.orgchart').data('options');
-        var count = 0;
-        buildChildNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function () {
-            if (++count === data.children.length) {
-                if (!$node.children('.bottomEdge').length) {
-                    $node.append('<i class="edge verticalEdge bottomEdge fa"></i>');
+                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 (!$node.find('.symbol').length) {
-                    $node.children('.title').prepend('<i class="fa ' + opts.parentNodeSymbol + ' symbol"></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>');
                 }
-                showChildren($node);
             }
-        });
-    }
-
-    // build the parent node of specific node
-    function buildParentNode($currentRoot, nodeData, opts, callback) {
-        var that = this;
-        var $table = $('<table>');
-        nodeData.relationship = nodeData.relationship || '001';
-        $.when(createNode(nodeData, 0, opts || $currentRoot.closest('.orgchart').data('options')))
-          .done(function ($nodeDiv) {
-              $table.append($nodeDiv.removeClass('slide-up').addClass('slide-down').wrap('<tr class="hidden"><td colspan="2"></td></tr>').closest('tr'));
-              $table.append('<tr class="lines hidden"><td colspan="2"><div class="downLine"></div></td></tr>');
-              var linesRow = '<td class="rightLine">&nbsp;</td><td class="leftLine">&nbsp;</td>';
-              $table.append('<tr class="lines hidden">' + linesRow + '</tr>');
-              var $oc = that.children('.orgchart');
-              $oc.prepend($table)
-                .children('table:first').append('<tr class="nodes"><td colspan="2"></td></tr>')
-                .children('tr:last').children().append($oc.children('table').last());
-              callback();
-          })
-          .fail(function () {
-              console.log('Failed to create parent node');
-          });
-    }
 
-    // exposed method
-    function addParent($currentRoot, data, opts) {
-        buildParentNode.call(this, $currentRoot, data, opts, function () {
-            if (!$currentRoot.children('.topEdge').length) {
-                $currentRoot.children('.title').after('<i class="edge verticalEdge topEdge fa"></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);
             }
-            showParent($currentRoot);
-        });
-    }
-
-    // subsequent processing of build sibling nodes
-    function complementLine($oneSibling, siblingCount, existingSibligCount) {
-        var lines = '';
-        for (var i = 0; i < existingSibligCount; i++) {
-            lines += '<td class="leftLine topLine">&nbsp;</td><td class="rightLine topLine">&nbsp;</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
-    function buildSiblingNode($nodeChart, nodeData, opts, callback) {
-        var opts = opts || $nodeChart.closest('.orgchart').data('options');
-        var newSiblingCount = nodeData.siblings ? nodeData.siblings.length : nodeData.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();
-            var childCount = 0;
-            buildChildNode.call($nodeChart.closest('.orgchart').parent(), $nodeChart.parent().closest('table'), nodeData, opts, function () {
-                if (++childCount === newSiblingCount) {
-                    var $siblingTds = $nodeChart.parent().closest('table').children('tr:last').children('td');
-                    if (existingSibligCount > 1) {
-                        complementLine($siblingTds.eq(0).before($nodeChart.closest('td').siblings().addBack().unwrap()), siblingCount, existingSibligCount);
-                        $siblingTds.addClass('hidden').find('.node').addClass('slide-left');
+            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 {
-                        complementLine($siblingTds.eq(insertPostion).after($nodeChart.closest('td').unwrap()), siblingCount, 1);
-                        $siblingTds.not(':eq(' + insertPostion + 1 + ')').addClass('hidden')
-                          .slice(0, insertPostion).find('.node').addClass('slide-right')
-                          .end().end().slice(insertPostion).find('.node').addClass('slide-left');
+                        $nodeWrapper.append($upperLines).append(lowerLines).append($nodesLayer);
                     }
-                    callback();
                 }
-            });
-        } else { // build the sibling nodes and parent node for the specific ndoe
-            var nodeCount = 0;
-            buildHierarchy($nodeChart.closest('.orgchart'), nodeData, 0, opts, function () {
-                if (++nodeCount === siblingCount) {
-                    complementLine($nodeChart.next().children('tr:last')
-                      .children().eq(insertPostion).after($('<td colspan="2">')
-                      .append($nodeChart)), siblingCount, 1);
-                    $nodeChart.closest('tr').siblings().eq(0).addClass('hidden').find('.node').addClass('slide-down');
-                    $nodeChart.parent().siblings().addClass('hidden')
-                      .slice(0, insertPostion).find('.node').addClass('slide-right')
-                      .end().end().slice(insertPostion).find('.node').addClass('slide-left');
-                    callback();
+                // 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);
                 }
-            });
-        }
-    }
-
-    function addSiblings($node, data, opts) {
-        buildSiblingNode.call($node.closest('.orgchart').parent(), $node.closest('table'), data, opts, function () {
+            } 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>');
             }
-            showSiblings($node);
-        });
-    }
-
-    function removeNodes($node) {
-        var $parent = $node.closest('table').parent();
-        var $sibs = $parent.parent().siblings();
-        if ($parent.is('td')) {
-            if (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();
+            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('.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 {
-                $sibs.eq(0).children().removeAttr('colspan')
-                  .find('.bottomEdge').remove()
-                  .end().end().siblings().remove();
+                $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;
             }
-        } else {
-            $parent.add($parent.siblings()).remove();
+            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');
+                });
         }
-    }
+    };
+
+    $.fn.orgchart = function (opts) {
+        return new OrgChart(this, opts).init();
+    };
 
 }));
diff --git a/atms-web/src/main/webapp/dist/index.html b/atms-web/src/main/webapp/dist/index.html
index 43120a70d39d9368e7c5f599455dd9c2cfcc8cae..defe87a88b31c52f9f66cd9079dc110316814ec7 100644
--- a/atms-web/src/main/webapp/dist/index.html
+++ b/atms-web/src/main/webapp/dist/index.html
@@ -4,7 +4,7 @@
   <meta charset="UTF-8">
   <title>2016 NBA Playoff Picture </title>
   <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'>
-  <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/orgchart/2.1.3/css/jquery.orgchart.min.css'>
+  <link rel='stylesheet' href='../Content/orgChart/jquery.orgchart.css'>
   <link rel="stylesheet" href="./style.css">
 </head>
 <body>
@@ -12,7 +12,7 @@
   <div id="chart-eastern" class="chart-container" style="text-align: left;"></div>
   <!-- <a id="github-link" href="https://github.com/dabeng/OrgChart" target="_blank"><i class="fa fa-github-square"></i></a> -->
   <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
-  <script src='https://cdnjs.cloudflare.com/ajax/libs/orgchart/2.1.3/js/jquery.orgchart.min.js'></script>
+  <script src='../Scripts/orgChart/jquery.orgchart.js'></script>
   <script src="./script.js"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/atms-web/src/main/webapp/dist2/index.html b/atms-web/src/main/webapp/dist2/index.html
index c5d02792cce7accc03f8a77c1dce9baada33553f..dcf2906276a41a88b286b519f5cce3832415d02b 100644
--- a/atms-web/src/main/webapp/dist2/index.html
+++ b/atms-web/src/main/webapp/dist2/index.html
@@ -8,6 +8,15 @@
 </head>
 <body>
 <div id="tree"></div>
+<canvas id="canvas" width="1000px" height="600px"></canvas>
+<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
+<!-- Required to convert named colors to RGB -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
+<!-- Optional if you want blur -->
+<script src="https://cdn.jsdelivr.net/npm/stackblur-canvas@^1/dist/stackblur.min.js"></script>
+<!-- Main canvg code -->
+<script src="https://cdn.jsdelivr.net/npm/canvg/dist/browser/canvg.min.js"></script>
+<script src="./d3.min.js"></script>
 <script src="./orgchart.js"></script>
 <script src="./script.js"></script>
 </body>
diff --git a/atms-web/src/main/webapp/dist2/script.js b/atms-web/src/main/webapp/dist2/script.js
index cf0c22e54e97747d742dec221ff1758868912855..21e886002b9423639cf6326c764ea44c16b56d98 100644
--- a/atms-web/src/main/webapp/dist2/script.js
+++ b/atms-web/src/main/webapp/dist2/script.js
@@ -1,19 +1,20 @@
-OrgChart.templates.myTemplate = Object.assign({}, OrgChart.templates.mery);
-OrgChart.templates.myTemplate.size = [400, 400];
-OrgChart.templates.myTemplate.node = '<rect width="400" height="200" style="fill:rgb(255,119,48);stroke-width:3px;stroke:rgba(153,10,10,0.01)" />';
-OrgChart.templates.myTemplate.ripple = {
-    radius: 100,
-    color: "#0890D3",
-    rect: null
-};
-
-OrgChart.templates.myTemplate.field_0 = '<text style="font-size: 24px;" fill="#ffffff" x="100" y="90" text-anchor="middle">{val}</text>';
+OrgChart.templates.myTemplate = Object.assign({}, OrgChart.templates.ana);
+OrgChart.templates.myTemplate.size = [200, 120];
+OrgChart.templates.myTemplate.node = '<rect width="200" height="100" style="fill:rgb(35,143,159);stroke-width:3px;stroke:rgba(153,10,10,0.01)" />';
+// OrgChart.templates.myTemplate.ripple = {
+//     radius: 100,
+//     color: "#0890D3",
+//     rect: null
+// };
+
+OrgChart.templates.myTemplate.field_0 = '<text style="font-size: 10px;" fill="#ffffff" x="100" y="90" text-anchor="middle">{val}</text>';
 OrgChart.templates.myTemplate.field_1 = '<text style="font-size: 16px;" fill="#ffffff" x="100" y="60" text-anchor="middle">{val}</text>';
 
 OrgChart.templates.myTemplate.img_0 = '<clipPath id="ulaImg"><circle cx="100" cy="150" r="0"></circle></clipPath>' +
     '<image preserveAspectRatio="xMidYMid slice" clip-path="url(#ulaImg)" xlink:href="{val}" x="60" y="110"  width="0" height="0"></image>';
 OrgChart.templates.myTemplate.link = '<path stroke="#686868" stroke-width="1px" fill="none" link-id="[{id}][{child-id}]" d="M{xa},{ya} L{xb},{yb} {xc},{yc} {xd},{yd}" />';
-OrgChart.templates.myTemplate.edge = '<path stroke="#686868" stroke-width="1px" fill="none" edge-id="[{id}][{child-id}]" d="M{xa},{ya} C{xb},{yb} {xc},{yc} {xd},{yd}"/>';
+OrgChart.templates.myTemplate.secondlink = '<path stroke="#686868" stroke-width="1px" fill="none" link-id="[{id}][{child-id}]" d="M{xa},{ya} L{xb},{yb} {xc},{yc} {xd},{yd}" />';
+// OrgChart.templates.myTemplate.edge = '<path stroke="#686868" stroke-width="1px" fill="none" edge-id="[{id}][{child-id}]" d="M{xa},{ya} C{xb},{yb} {xc},{yc} {xd},{yd}"/>';
 
 OrgChart.templates.myTemplate.plus =
     '<rect x="0" y="0" width="36" height="36" rx="12" ry="12" fill="#2E2E2E" stroke="#aeaeae" stroke-width="1"></rect>'
@@ -24,40 +25,41 @@ OrgChart.templates.myTemplate.minus =
     '<rect x="0" y="0" width="36" height="36" rx="12" ry="12" fill="#2E2E2E" stroke="#aeaeae" stroke-width="1"></rect>'
     + '<line x1="4" y1="18" x2="32" y2="18" stroke-width="1" stroke="#aeaeae"></line>';
 
-OrgChart.templates.myTemplate.expandCollapseSize = 36;
-
-OrgChart.templates.myTemplate.nodeMenuButton = '<g style="cursor:pointer;" transform="matrix(1,0,0,1,93,15)" control-node-menu-id="{id}">' +
-    '<rect x="-4" y="-10" fill="#000000" fill-opacity="0" width="22" height="22"></rect>' +
-    '<line x1="0" y1="0" x2="0" y2="10" stroke-width="2" stroke="#0890D3" />' +
-    '<line x1="7" y1="0" x2="7" y2="10" stroke-width="2" stroke="#0890D3" />' +
-    '<line x1="14" y1="0" x2="14" y2="10" stroke-width="2" stroke="#0890D3" /></g>';
-
-OrgChart.templates.myTemplate.exportMenuButton = '<div style="position:absolute;right:{p}px;top:{p}px; width:40px;height:50px;cursor:pointer;" control-export-menu="">' +
-    '<hr style="background-color: #0890D3; height: 3px; border: none;">' +
-    '<hr style="background-color: #0890D3; height: 3px; border: none;">' +
-    '<hr style="background-color: #0890D3; height: 3px; border: none;"></div>';
-
-OrgChart.templates.myTemplate.pointer = '<g data-pointer="pointer" transform="matrix(0,0,0,0,100,100)">' +
-    '<g transform="matrix(0.3,0,0,0.3,-17,-17)">' +
-    '<polygon fill="#0890D3" points="53.004,173.004 53.004,66.996 0,120"/>' +
-    '<polygon fill="#0890D3" points="186.996,66.996 186.996,173.004 240,120"/>' +
-    '<polygon fill="#0890D3" points="66.996,53.004 173.004,53.004 120,0"/>' +
-    '<polygon fill="#0890D3" points="120,240 173.004,186.996 66.996,186.996"/>' +
-    '<circle fill="#0890D3" cx="120" cy="120" r="30"/></g></g>';
-OrgChart.templates.ana.field_0 = '<text class="field_0"  style="font-size: 20px;" fill="#ffffff" x="125" y="60" text-anchor="middle">{val}</text>';
+// OrgChart.templates.myTemplate.expandCollapseSize = 10;
+
+// OrgChart.templates.myTemplate.nodeMenuButton = '<g style="cursor:pointer;" transform="matrix(1,0,0,1,93,15)" control-node-menu-id="{id}">' +
+//     '<rect x="-4" y="-10" fill="#000000" fill-opacity="0" width="22" height="22"></rect>' +
+//     '<line x1="0" y1="0" x2="0" y2="10" stroke-width="2" stroke="#0890D3" />' +
+//     '<line x1="7" y1="0" x2="7" y2="10" stroke-width="2" stroke="#0890D3" />' +
+//     '<line x1="14" y1="0" x2="14" y2="10" stroke-width="2" stroke="#0890D3" /></g>';
+
+// OrgChart.templates.myTemplate.exportMenuButton = '<div style="position:absolute;right:{p}px;top:{p}px; width:40px;height:50px;cursor:pointer;" control-export-menu="">' +
+//     '<hr style="background-color: #0890D3; height: 3px; border: none;">' +
+//     '<hr style="background-color: #0890D3; height: 3px; border: none;">' +
+//     '<hr style="background-color: #0890D3; height: 3px; border: none;"></div>';
+
+// OrgChart.templates.myTemplate.pointer = '<g data-pointer="pointer" transform="matrix(0,0,0,0,100,100)">' +
+//     '<g transform="matrix(0.3,0,0,0.3,-17,-17)">' +
+//     '<polygon fill="#0890D3" points="53.004,173.004 53.004,66.996 0,120"/>' +
+//     '<polygon fill="#0890D3" points="186.996,66.996 186.996,173.004 240,120"/>' +
+//     '<polygon fill="#0890D3" points="66.996,53.004 173.004,53.004 120,0"/>' +
+//     '<polygon fill="#0890D3" points="120,240 173.004,186.996 66.996,186.996"/>' +
+//     '<circle fill="#0890D3" cx="120" cy="120" r="30"/></g></g>';
+// OrgChart.templates.ana.field_0 = '<text class="field_0"  style="font-size: 20px;" fill="#ffffff" x="125" y="60" text-anchor="middle">{val}</text>';
 var chart = new OrgChart(document.getElementById("tree"), {
+    template: "myTemplate",
     enableSearch: false,
-    // nodeMenu: {
-    //     add: {text: "Add"},
-    //     edit: {text: "Edit"},
-    //     remove: {text: "Remove"}
-    // },
-    // menu: {
-    //     pdf: {text: "Export PDF"},
-    //     png: {text: "Export PNG"},
-    //     svg: {text: "Export SVG"},
-    //     csv: {text: "Export CSV"}
-    // },
+    nodeMenu: {
+        add: {text: "Add"},
+        edit: {text: "Edit"},
+        remove: {text: "Remove"}
+    },
+    menu: {
+        pdf: {text: "Export PDF"},
+        png: {text: "Export PNG"},
+        svg: {text: "Export SVG"},
+        csv: {text: "Export CSV"}
+    },
     linkBinding: {
         link_field_0: "percent"
     },
@@ -67,8 +69,133 @@ var chart = new OrgChart(document.getElementById("tree"), {
         // img_0: "img"
     },
     nodes: [
-        {id: 1, name: "Amber McKenzie", title: "CEO", img: "//balkangraph.com/js/img/1.jpg",percent:"100%"},
-        {id: 2, pid: 1, name: "Ava Field", title: "IT Manager", img: "//balkangraph.com/js/img/2.jpg", mobile: "0878108255",percent:"100%"},
-        {id: 3, pid: 1, name: "Peter Stevens", title: "HR Manager", img: "//balkangraph.com/js/img/3.jpg",percent:"100%"}
-    ]
-});
\ No newline at end of file
+        {id: 4, name: "Amber McKenzie", title: "CEO", img: "//balkangraph.com/js/img/1.jpg", percent: "100%"},
+        {id: 5, name: "Amber McKenzie", title: "CEO", img: "//balkangraph.com/js/img/1.jpg", percent: "100%"},
+        {id: 6, name: "Amber McKenzie", title: "CEO", img: "//balkangraph.com/js/img/1.jpg", percent: "100%"},
+        {id: 1, pid: 4, spids: [5,6], name: "Amber McKenzie", title: "CEO", img: "//balkangraph.com/js/img/1.jpg", percent: "100%"},
+        {id: 2, pid: 1, name: "Ava Field", title: "IT Manager", img: "//balkangraph.com/js/img/2.jpg", mobile: "0878108255", percent: "100%"},
+        {id: 3, pid: 1, name: "Peter Stevens", title: "HR Manager", img: "//balkangraph.com/js/img/3.jpg", percent: "100%"}
+
+    ],
+    onExportStart: exportStart
+});
+
+// d3.select("svg").append("circle")
+//     .attr("r", "10")
+//     .attr("style", "fill:white;stroke:black;stroke-width:5");
+function makeSVG(tag, attrs) {
+    var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
+    for (var k in attrs)
+        el.setAttribute(k, attrs[k]);
+    return el;
+}
+
+var initialized = false;
+
+
+// var circle = makeSVG('image', {"href": '../dist/2.png', height: 115, width: 250});
+var polygon = makeSVG('polygon', {"points": '100,0 0,100 200,100', style: "fill:lime;stroke:purple;stroke-width:1", height: 115, width: 250});
+var ellipse = makeSVG('ellipse', {"cx": '100', "cy":"50","rx":"100","ry":"50", style: "fill:blue;stroke:purple;stroke-width:2", height: 115, width: 250});
+var ellipse1 = makeSVG('ellipse', {"cx": '100', "cy":"50","rx":"100","ry":"50", style: "fill:blue;stroke:purple;stroke-width:2", height: 115, width: 250});
+var ellipse2 = makeSVG('ellipse', {"cx": '100', "cy":"50","rx":"100","ry":"50", style: "fill:blue;stroke:purple;stroke-width:2", height: 115, width: 250});
+// setTimeout(function() {
+//     $("svg>g[node-id=1]>rect").css("fill","transparent").css("stroke-width",0);
+//     $("svg>g[node-id=1]>rect").after(circle);
+// },3000);
+// circle.onmousedown = function () {
+//     //alert('hello');
+// };
+
+chart.on('redraw', function () {
+    if (!initialized) {
+        //第一次在加载时的元素
+        // $("svg>g[node-id=1]>rect").css("fill","transparent").css("stroke-width",0);
+        // $("svg>g[node-id=1]>rect").after(circle);
+
+        //load '../path/to/your.svg' in the canvas with id = 'canvas'
+        // canvg('canvas', '../path/to/your.svg')
+        //
+        // //load a svg snippet in the canvas with id = 'drawingArea'
+        // canvg(document.getElementById('drawingArea'), $('svg')[0].outerHTML);
+        //
+        // //ignore mouse events and animation
+        // canvg('canvas', 'file.svg', { ignoreMouse: true, ignoreAnimation: true });
+        initialized = true;
+    }
+    // $("svg>g[node-id=1]>rect").css("fill", "transparent").css("stroke-width", 0).after(polygon);
+    $("svg>g[node-id=1]>rect").after(polygon);
+    $("svg>g[node-id=4]>rect").after(ellipse);
+    $("svg>g[node-id=5]>rect").after(ellipse1);
+    $("svg>g[node-id=6]>rect").after(ellipse2);
+
+    // $("svg>g[node-id=5]").attr("transform","matrix(1, 0, 0, 1, 360, 0)");
+    // $("svg>g[node-id=6]").attr("transform","matrix(1, 0, 0, 1, 590, 0)");
+    $("svg>g[second-link-id='[1][5]']>path").attr("d","M210,180 L210,160 L440,160 L440,102");
+    $("svg>g[second-link-id='[1][6]']>path").attr("d","M210,180 L210,160 L660,160 L660,102");
+    //                                        d: path("M240 180 L240 160 L680 160 L680 120");
+});
+
+
+chart.on('click', function (sender, node) {
+    return false;
+});
+// function click(sender, node) {
+//     /*  c.innerHTML += "click(sender, node)<br />"; */
+//     return false;
+// };
+
+function exportStart(sender, options, svg) {
+    // $("svg>g[node-id=1]>rect").css("fill", "transparent").css("stroke-width", 0);
+    // $("svg>g[node-id=1]>rect").after(circle);
+    // options.content = $('svg')[0].outerHTML.replace("../dist/2.png",urlData);
+
+    // var svg = $('svg')[0].outerHTML.replace("../dist/2.png",urlData);
+    // var svg = $('svg')[0].outerHTML.replace("../dist/2.png",urlData);
+    // var svg = options.content;
+    var svg = $('svg')[0].outerHTML;
+    console.log("svg", svg);
+    canvg(document.getElementById('canvas'), svg);
+    var canvas = document.getElementById("canvas");
+    // var context = canvas.getContext("2d");
+    // context.fillStyle = "green";
+    // context.fillRect(50, 50, 100, 100);
+    // no argument defaults to image/png; image/jpeg, etc also work on some
+    // implementations -- image/png is the only one that must be supported per spec.
+    // window.location = canvas.toDataURL("image/png");
+    // var image = canvas.toDataURL("image/png");
+    // document.write('<img src="'+image+'"/>');
+    var a = document.createElement("a");
+    a.download = "fallback.png";
+    a.href = canvas.toDataURL("image/png");
+    a.click();
+    return false;
+}
+
+function toDataURL(url, callback) {
+    var httpRequest = new XMLHttpRequest();
+    httpRequest.onload = function () {
+        var fileReader = new FileReader();
+        fileReader.onloadend = function () {
+            callback(fileReader.result);
+        };
+        fileReader.readAsDataURL(httpRequest.response);
+    };
+    httpRequest.open('GET', url);
+    httpRequest.responseType = 'blob';
+    httpRequest.send();
+}
+
+// var urlData;
+// toDataURL('../dist/2.png', function (dataUrl) {
+//     urlData = dataUrl;
+//     console.log('Result in string:', dataUrl);
+// });
+
+
+// setTimeout(function(){ $("div#tree>svg")[0].children[2].append('<image id="img" xlink:href="http://localhost:63342/LEMS/atms-web/src/main/webapp/dist/2.png" height="115" width="250"></image>'); }, 3000);
+// setTimeout(function(){ $("div#tree>svg")[0].children[2].append( "<strong>Hello</strong>" ); }, 3000);
+// .setAttribute("fill","url(../dist/2.png)");
+// <image xlink:href="http://localhost:63342/LEMS/atms-web/src/main/webapp/dist/2.png" height="200" width="200" style="
+// width: 250px;
+// height: 115px;
+// "></image>