orgchart 4.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.github/workflows/publish.yml +32 -0
  2. package/README.md +205 -89
  3. package/README.zh-cn.md +221 -68
  4. package/README.zh-tw.md +221 -68
  5. package/bower.json +6 -9
  6. package/demo/10000-nodes.html +139 -0
  7. package/demo/ajax-datasource.html +46 -45
  8. package/demo/bottom2top.html +12 -9
  9. package/demo/center-on-selected-node.html +21 -17
  10. package/demo/color-coded.html +12 -9
  11. package/demo/css/style.css +28 -0
  12. package/demo/custom-icons.html +12 -9
  13. package/demo/custom-template.html +12 -9
  14. package/demo/data-prop-compact.html +11 -8
  15. package/demo/data-prop-hybrid.html +79 -50
  16. package/demo/default-collapsed.html +12 -9
  17. package/demo/drag-drop-hybrid-chart.html +17 -14
  18. package/demo/drag-drop.html +21 -16
  19. package/demo/edit-chart.html +148 -94
  20. package/demo/edit-hybrid-chart.html +178 -135
  21. package/demo/expand-to-given-depth.html +30 -22
  22. package/demo/export-chart-pdf.html +12 -9
  23. package/demo/export-chart-with-pictures.html +19 -12
  24. package/demo/export-chart.html +12 -9
  25. package/demo/family-tree.html +100 -0
  26. package/demo/familytree-custom-properties.html +108 -0
  27. package/demo/filter-node.html +104 -57
  28. package/demo/get-hierarchy.html +17 -12
  29. package/demo/get-related-nodes.html +27 -17
  30. package/demo/index.html +7 -4
  31. package/demo/integrate-map.html +32 -27
  32. package/demo/left2right.html +12 -9
  33. package/demo/level-offset.html +16 -15
  34. package/demo/link-node.html +12 -9
  35. package/demo/local-datasource.html +11 -8
  36. package/demo/minimap.html +224 -0
  37. package/demo/nodes-of-different-widths.html +11 -8
  38. package/demo/ondemand-loading-data.html +88 -91
  39. package/demo/option-createNode.html +31 -20
  40. package/demo/pan-zoom.html +20 -11
  41. package/demo/reload-data.html +17 -14
  42. package/demo/report-path.html +59 -29
  43. package/demo/responsive-design.html +14 -11
  44. package/demo/right2left.html +12 -9
  45. package/demo/toggle-sibs-resp.html +12 -9
  46. package/demo/ul-datasource.html +11 -8
  47. package/demo/vertical-level.html +12 -9
  48. package/dist/css/{jquery.orgchart.css → orgchart.css} +210 -78
  49. package/dist/css/orgchart.min.css +1 -0
  50. package/dist/js/orgchart.js +4101 -0
  51. package/dist/js/orgchart.min.js +2 -0
  52. package/dist/js/orgchart.min.js.map +1 -0
  53. package/gulpfile.js +37 -26
  54. package/package.json +32 -32
  55. package/src/css/{jquery.orgchart.css → orgchart.css} +210 -78
  56. package/src/js/orgchart.js +4101 -0
  57. package/test/cypress/e2e/demo-interactions.cy.js +91 -0
  58. package/test/cypress/e2e/demo-rendering.cy.js +100 -0
  59. package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +22 -0
  60. package/test/cypress/e2e/edit-chart.cy.js +20 -0
  61. package/test/cypress/e2e/level-offset.cy.js +21 -0
  62. package/test/cypress/e2e/minimap.cy.js +66 -0
  63. package/test/cypress/e2e/pan-zoom.cy.js +165 -0
  64. package/test/cypress/e2e/report-path.cy.js +20 -0
  65. package/test/cypress/e2e/vertical-vevel.cy.js +5 -3
  66. package/test/e2e/local-datasource/test.js +1 -1
  67. package/test/integration/init-tests.js +168 -62
  68. package/test/integration/tests.js +1034 -56
  69. package/test/unit/lifecycle-builders-export.tests.js +1426 -0
  70. package/test/unit/minimap.tests.js +210 -0
  71. package/test/unit/tests.js +2372 -252
  72. package/demo/multiple-layers.html +0 -128
  73. package/dist/css/jquery.orgchart.min.css +0 -1
  74. package/dist/js/jquery.orgchart.js +0 -1686
  75. package/dist/js/jquery.orgchart.min.js +0 -5
  76. package/dist/js/jquery.orgchart.min.js.map +0 -1
  77. package/src/js/jquery.orgchart.js +0 -1686
@@ -1,1686 +0,0 @@
1
- /*
2
- * jQuery OrgChart Plugin
3
- * https://github.com/dabeng/OrgChart
4
- *
5
- * Copyright 2016, dabeng
6
- * https://github.com/dabeng
7
- *
8
- * Licensed under the MIT license:
9
- * http://www.opensource.org/licenses/MIT
10
- */
11
- 'use strict';
12
-
13
- (function (factory) {
14
- if (typeof module === 'object' && typeof module.exports === 'object') {
15
- factory(require('jquery'), window, document);
16
- } else {
17
- factory(jQuery, window, document);
18
- }
19
- }(function ($, window, document, undefined) {
20
- var OrgChart = function (elem, opts) {
21
- this.$chartContainer = $(elem);
22
- this.opts = opts;
23
- this.defaultOptions = {
24
- 'icons': {
25
- 'theme': 'oci',
26
- 'parentNode': 'oci-menu',
27
- 'expandToUp': 'oci-chevron-up',
28
- 'collapseToDown': 'oci-chevron-down',
29
- 'collapseToLeft': 'oci-chevron-left',
30
- 'expandToRight': 'oci-chevron-right',
31
- 'backToCompact': 'oci-corner-top-left',
32
- 'backToLoose': 'oci-corner-bottom-right',
33
- 'collapsed': 'oci-plus-square',
34
- 'expanded': 'oci-minus-square',
35
- 'spinner': 'oci-spinner'
36
- },
37
- 'nodeTitle': 'name',
38
- 'nodeId': 'id',
39
- 'toggleSiblingsResp': false,
40
- 'visibleLevel': 999,
41
- 'chartClass': '',
42
- 'exportButton': false,
43
- 'exportButtonName': 'Export',
44
- 'exportFilename': 'OrgChart',
45
- 'exportFileextension': 'png',
46
- 'draggable': false,
47
- 'direction': 't2b',
48
- 'pan': false,
49
- 'zoom': false,
50
- 'zoominLimit': 7,
51
- 'zoomoutLimit': 0.5
52
- };
53
- };
54
- //
55
- OrgChart.prototype = {
56
- //
57
- init: function (opts) {
58
- var that = this;
59
- this.options = $.extend({}, this.defaultOptions, this.opts, opts);
60
- // build the org-chart
61
- var $chartContainer = this.$chartContainer;
62
- if (this.$chart) {
63
- this.$chart.remove();
64
- }
65
- var data = this.options.data;
66
- var $chart = this.$chart = $('<div>', {
67
- 'data': { 'options': this.options },
68
- 'class': 'orgchart' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '') + (this.options.direction !== 't2b' ? ' ' + this.options.direction : ''),
69
- 'click': function(event) {
70
- if (!$(event.target).closest('.node').length) {
71
- $chart.find('.node.focused').removeClass('focused');
72
- }
73
- }
74
- });
75
- if (typeof MutationObserver !== 'undefined') {
76
- this.triggerInitEvent();
77
- }
78
- var $root = $chart.append($('<ul class="nodes"><li class="hierarchy"></li></ul>')).find('.hierarchy');
79
-
80
- if (data instanceof $) { // ul datasource
81
- this.buildHierarchy($root, this.buildJsonDS(data.children()), 0, this.options);
82
- } else { // local json datasource
83
- if (data.relationship) {
84
- this.buildHierarchy($root, data);
85
- } else {
86
- this.buildHierarchy($root, this.attachRel(data, '00'));
87
- }
88
- }
89
-
90
- $chartContainer.append($chart);
91
-
92
- // append the export button
93
- if (this.options.exportButton && !$('.oc-export-btn').length) {
94
- this.attachExportButton();
95
- }
96
-
97
- if (this.options.pan) {
98
- this.bindPan();
99
- }
100
-
101
- if (this.options.zoom) {
102
- this.bindZoom();
103
- }
104
-
105
- return this;
106
- },
107
- handleCompactNodes: function () {
108
- // caculate the compact nodes' level which is used to add different styles
109
- this.$chart.find('.node.compact')
110
- .each((index, node) => {
111
- $(node).addClass($(node).parents('.compact').length % 2 === 0 ? 'even' : 'odd');
112
- }); // the following code snippets is used to add direction arrows for the most top compact node, however the styles is not adjusted correctly
113
- // .filter((index, node) => !$(node).parent().is('.compact'))
114
- // .each((index, node) => {
115
- // $(node).append(`<i class="edge verticalEdge topEdge ${this.options.icons.theme}"></i>`);
116
- // if (this.getSiblings($(node)).length) {
117
- // $(node).append(`<i class="edge horizontalEdge rightEdge ${this.options.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${this.options.icons.theme}"></i>`);
118
- // }
119
- // });
120
- },
121
- //
122
- triggerInitEvent: function () {
123
- var that = this;
124
- var mo = new MutationObserver(function (mutations) {
125
- mo.disconnect();
126
- initTime:
127
- for (var i = 0; i < mutations.length; i++) {
128
- for (var j = 0; j < mutations[i].addedNodes.length; j++) {
129
- if (mutations[i].addedNodes[j].classList.contains('orgchart')) {
130
- that.handleCompactNodes();
131
- if (that.options.initCompleted && typeof that.options.initCompleted === 'function') {
132
- that.options.initCompleted(that.$chart);
133
- }
134
- var initEvent = $.Event('init.orgchart');
135
- that.$chart.trigger(initEvent);
136
- break initTime;
137
- }
138
- }
139
- }
140
- });
141
- mo.observe(this.$chartContainer[0], { childList: true });
142
- },
143
- triggerShowEvent: function ($target, rel) {
144
- var initEvent = $.Event('show-' + rel + '.orgchart');
145
- $target.trigger(initEvent);
146
- },
147
- triggerHideEvent: function ($target, rel) {
148
- var initEvent = $.Event('hide-' + rel + '.orgchart');
149
- $target.trigger(initEvent);
150
- },
151
- // add export button for orgchart
152
- attachExportButton: function () {
153
- var that = this;
154
- var $exportBtn = $('<button>', {
155
- 'class': 'oc-export-btn',
156
- 'text': this.options.exportButtonName,
157
- 'click': function(e) {
158
- e.preventDefault();
159
- that.export();
160
- }
161
- });
162
- this.$chartContainer.after($exportBtn);
163
- },
164
- setOptions: function (opts, val) {
165
- if (typeof opts === 'string') {
166
- if (opts === 'pan') {
167
- if (val) {
168
- this.bindPan();
169
- } else {
170
- this.unbindPan();
171
- }
172
- }
173
- if (opts === 'zoom') {
174
- if (val) {
175
- this.bindZoom();
176
- } else {
177
- this.unbindZoom();
178
- }
179
- }
180
- }
181
- if (typeof opts === 'object') {
182
- if (opts.data) {
183
- this.init(opts);
184
- } else {
185
- if (typeof opts.pan !== 'undefined') {
186
- if (opts.pan) {
187
- this.bindPan();
188
- } else {
189
- this.unbindPan();
190
- }
191
- }
192
- if (typeof opts.zoom !== 'undefined') {
193
- if (opts.zoom) {
194
- this.bindZoom();
195
- } else {
196
- this.unbindZoom();
197
- }
198
- }
199
- }
200
- }
201
-
202
- return this;
203
- },
204
- //
205
- panStartHandler: function (e) {
206
- var $chart = $(e.delegateTarget);
207
- if ($(e.target).closest('.node').length || (e.touches && e.touches.length > 1)) {
208
- $chart.data('panning', false);
209
- return;
210
- } else {
211
- $chart.css('cursor', 'move').data('panning', true);
212
- }
213
- var lastX = 0;
214
- var lastY = 0;
215
- var lastTf = $chart.css('transform');
216
- if (lastTf !== 'none') {
217
- var temp = lastTf.split(',');
218
- if (lastTf.indexOf('3d') === -1) {
219
- lastX = parseInt(temp[4]);
220
- lastY = parseInt(temp[5]);
221
- } else {
222
- lastX = parseInt(temp[12]);
223
- lastY = parseInt(temp[13]);
224
- }
225
- }
226
- var startX = 0;
227
- var startY = 0;
228
- if (!e.targetTouches) { // pand on desktop
229
- startX = e.pageX - lastX;
230
- startY = e.pageY - lastY;
231
- } else if (e.targetTouches.length === 1) { // pan on mobile device
232
- startX = e.targetTouches[0].pageX - lastX;
233
- startY = e.targetTouches[0].pageY - lastY;
234
- } else if (e.targetTouches.length > 1) {
235
- return;
236
- }
237
- $chart.on('mousemove touchmove',function(e) {
238
- if (!$chart.data('panning')) {
239
- return;
240
- }
241
- var newX = 0;
242
- var newY = 0;
243
- if (!e.targetTouches) { // pand on desktop
244
- newX = e.pageX - startX;
245
- newY = e.pageY - startY;
246
- } else if (e.targetTouches.length === 1) { // pan on mobile device
247
- newX = e.targetTouches[0].pageX - startX;
248
- newY = e.targetTouches[0].pageY - startY;
249
- } else if (e.targetTouches.length > 1) {
250
- return;
251
- }
252
- var lastTf = $chart.css('transform');
253
- if (lastTf === 'none') {
254
- if (lastTf.indexOf('3d') === -1) {
255
- $chart.css('transform', 'matrix(1, 0, 0, 1, ' + newX + ', ' + newY + ')');
256
- } else {
257
- $chart.css('transform', 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + newX + ', ' + newY + ', 0, 1)');
258
- }
259
- } else {
260
- var matrix = lastTf.split(',');
261
- if (lastTf.indexOf('3d') === -1) {
262
- matrix[4] = ' ' + newX;
263
- matrix[5] = ' ' + newY + ')';
264
- } else {
265
- matrix[12] = ' ' + newX;
266
- matrix[13] = ' ' + newY;
267
- }
268
- $chart.css('transform', matrix.join(','));
269
- }
270
- });
271
- },
272
- //
273
- panEndHandler: function (e) {
274
- if (e.data.chart.data('panning')) {
275
- e.data.chart.data('panning', false).css('cursor', 'default').off('mousemove');
276
- }
277
- },
278
- //
279
- bindPan: function () {
280
- this.$chartContainer.css('overflow', 'hidden');
281
- this.$chart.on('mousedown touchstart', this.panStartHandler);
282
- $(document).on('mouseup touchend', { 'chart': this.$chart }, this.panEndHandler);
283
- },
284
- //
285
- unbindPan: function () {
286
- this.$chartContainer.css('overflow', 'auto');
287
- this.$chart.off('mousedown touchstart', this.panStartHandler);
288
- $(document).off('mouseup touchend', this.panEndHandler);
289
- },
290
- //
291
- zoomWheelHandler: function (e) {
292
- var oc = e.data.oc;
293
- e.preventDefault();
294
- var newScale = 1 + (e.originalEvent.deltaY > 0 ? -0.2 : 0.2);
295
- oc.setChartScale(oc.$chart, newScale);
296
- },
297
- //
298
- zoomStartHandler: function (e) {
299
- if(e.touches && e.touches.length === 2) {
300
- var oc = e.data.oc;
301
- oc.$chart.data('pinching', true);
302
- var dist = oc.getPinchDist(e);
303
- oc.$chart.data('pinchDistStart', dist);
304
- }
305
- },
306
- zoomingHandler: function (e) {
307
- var oc = e.data.oc;
308
- if(oc.$chart.data('pinching')) {
309
- var dist = oc.getPinchDist(e);
310
- oc.$chart.data('pinchDistEnd', dist);
311
- }
312
- },
313
- zoomEndHandler: function (e) {
314
- var oc = e.data.oc;
315
- if(oc.$chart.data('pinching')) {
316
- oc.$chart.data('pinching', false);
317
- var diff = oc.$chart.data('pinchDistEnd') - oc.$chart.data('pinchDistStart');
318
- if (diff > 0) {
319
- oc.setChartScale(oc.$chart, 1.2);
320
- } else if (diff < 0) {
321
- oc.setChartScale(oc.$chart, 0.8);
322
- }
323
- }
324
- },
325
- //
326
- bindZoom: function () {
327
- this.$chartContainer.on('wheel', { 'oc': this }, this.zoomWheelHandler);
328
- this.$chartContainer.on('touchstart', { 'oc': this }, this.zoomStartHandler);
329
- $(document).on('touchmove', { 'oc': this }, this.zoomingHandler);
330
- $(document).on('touchend', { 'oc': this }, this.zoomEndHandler);
331
- },
332
- unbindZoom: function () {
333
- this.$chartContainer.off('wheel', this.zoomWheelHandler);
334
- this.$chartContainer.off('touchstart', this.zoomStartHandler);
335
- $(document).off('touchmove', this.zoomingHandler);
336
- $(document).off('touchend', this.zoomEndHandler);
337
- },
338
- //
339
- getPinchDist: function (e) {
340
- return Math.sqrt((e.touches[0].clientX - e.touches[1].clientX) * (e.touches[0].clientX - e.touches[1].clientX) +
341
- (e.touches[0].clientY - e.touches[1].clientY) * (e.touches[0].clientY - e.touches[1].clientY));
342
- },
343
- //
344
- setChartScale: function ($chart, newScale) {
345
- var opts = $chart.data('options');
346
- var lastTf = $chart.css('transform');
347
- var matrix = '';
348
- var targetScale = 1;
349
- if (lastTf === 'none') {
350
- $chart.css('transform', 'scale(' + newScale + ',' + newScale + ')');
351
- } else {
352
- matrix = lastTf.split(',');
353
- if (lastTf.indexOf('3d') === -1) {
354
- targetScale = Math.abs(window.parseFloat(matrix[3]) * newScale);
355
- if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) {
356
- $chart.css('transform', lastTf + ' scale(' + newScale + ',' + newScale + ')');
357
- }
358
- } else {
359
- targetScale = Math.abs(window.parseFloat(matrix[1]) * newScale);
360
- if (targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit) {
361
- $chart.css('transform', lastTf + ' scale3d(' + newScale + ',' + newScale + ', 1)');
362
- }
363
- }
364
- }
365
- },
366
- //
367
- buildJsonDS: function ($li) {
368
- var that = this;
369
- var subObj = {
370
- 'name': $li.contents().eq(0).text().trim(),
371
- 'relationship': ($li.parent().parent().is('li') ? '1': '0') + ($li.siblings('li').length ? 1: 0) + ($li.children('ul').length ? 1 : 0)
372
- };
373
- $.each($li.data(), function(key, value) {
374
- subObj[key] = value;
375
- });
376
- $li.children('ul').children().each(function() {
377
- if (!subObj.children) { subObj.children = []; }
378
- subObj.children.push(that.buildJsonDS($(this)));
379
- });
380
- return subObj;
381
- },
382
- // process datasource and add necessary information
383
- attachRel: function (data, flags) {
384
- var that = this;
385
- data.relationship = flags + (data.children && data.children.length > 0 ? 1 : 0);
386
- if (this.options?.compact?.constructor === Function && this.options.compact(data)) {
387
- data.compact = true;
388
- }
389
- if (data.children) {
390
- data.children.forEach(function(item) {
391
- if (data.hybrid || data.vertical) { // identify all the descendant nodes except the root node of hybrid structure
392
- item.vertical = true;
393
- } else if (data.compact && item.children) { // identify all the compact ancestor nodes
394
- item.compact = true;
395
- } else if (data.compact && !item.children) { // identify all the compact descendant nodes
396
- item.associatedCompact = true;
397
- }
398
- that.attachRel(item, '1' + (data.children.length > 1 ? 1 : 0));
399
- });
400
- }
401
- return data;
402
- },
403
- //
404
- loopChart: function ($chart, includeNodeData) {
405
- includeNodeData = (includeNodeData !== null && includeNodeData !== undefined) ? includeNodeData : false;
406
- var that = this;
407
- var $node = $chart.find('.node:first');
408
- var subObj = { 'id': $node[0].id };
409
- if (includeNodeData) {
410
- $.each($node.data('nodeData'), function (key, value) {
411
- subObj[key] = value;
412
- });
413
- }
414
- $node.siblings('.nodes').children().each(function() {
415
- if (!subObj.children) { subObj.children = []; }
416
- subObj.children.push(that.loopChart($(this), includeNodeData));
417
- });
418
- return subObj;
419
- },
420
- //
421
- getHierarchy: function (includeNodeData) {
422
- includeNodeData = (includeNodeData !== null && includeNodeData !== undefined) ? includeNodeData : false;
423
- if (typeof this.$chart === 'undefined') {
424
- return 'Error: orgchart does not exist'
425
- } else {
426
- if (!this.$chart.find('.node').length) {
427
- return 'Error: nodes do not exist'
428
- } else {
429
- var valid = true;
430
- this.$chart.find('.node').each(function () {
431
- if (!this.id) {
432
- valid = false;
433
- return false;
434
- }
435
- });
436
- if (!valid) {
437
- return 'Error: All nodes of orghcart to be exported must have data-id attribute!';
438
- }
439
- }
440
- }
441
- return this.loopChart(this.$chart, includeNodeData);
442
- },
443
- // detect the exist/display state of related node
444
- getNodeState: function ($node, relation) {
445
- var $target = {};
446
- var isVerticalNode = !!$node.closest('vertical').length;
447
- var relation = relation || 'self';
448
- if (relation === 'parent') {
449
- if (isVerticalNode) {
450
- $target = $node.closest('ul').parents('ul');
451
- if (!$target.length) {
452
- $target = $node.closest('.nodes');
453
- if (!$target.length) {
454
- $target = $node.closest('.vertical').siblings(':first');
455
- }
456
- }
457
- } else {
458
- $target = $node.closest('.nodes').siblings('.node');
459
- }
460
- if ($target.length) {
461
- if ($target.is('.hidden') || (!$target.is('.hidden') && $target.closest('.nodes').is('.hidden')) || (!$target.is('.hidden') && $target.closest('.vertical').is('.hidden'))) {
462
- return { 'exist': true, 'visible': false };
463
- }
464
- return { 'exist': true, 'visible': true };
465
- }
466
- } else if (relation === 'children') {
467
- $target = isVerticalNode ? $node.parent().children('ul') : $node.siblings('.nodes');
468
- if ($target.length) {
469
- if (!$target.is('.hidden')) {
470
- return { 'exist': true, 'visible': true };
471
- }
472
- return { 'exist': true, 'visible': false };
473
- }
474
- } else if (relation === 'siblings') {
475
- $target = isVerticalNode ? $node.closest('ul') : $node.parent().siblings();
476
- if ($target.length && (!isVerticalNode || $target.children('li').length > 1)) {
477
- if (!$target.is('.hidden') && !$target.parent().is('.hidden') && (!isVerticalNode || !$target.closest('.vertical').is('.hidden'))) {
478
- return { 'exist': true, 'visible': true };
479
- }
480
- return { 'exist': true, 'visible': false };
481
- }
482
- } else {
483
- $target = $node;
484
- if ($target.length) {
485
- if (!(($target.closest('.nodes').length && $target.closest('.nodes').is('.hidden')) ||
486
- ($target.closest('.hierarchy').length && $target.closest('.hierarchy').is('.hidden')) ||
487
- ($target.closest('.vertical').length && ($target.closest('.nodes').is('.hidden') || $target.closest('.vertical').is('.hidden')))
488
- )) {
489
- return { 'exist': true, 'visible': true };
490
- }
491
- return { 'exist': true, 'visible': false };
492
- }
493
- }
494
- return { 'exist': false, 'visible': false };
495
- },
496
- getParent: function ($node) {
497
- return this.getRelatedNodes($node, 'parent');
498
- },
499
- getChildren: function ($node) {
500
- return this.getRelatedNodes($node, 'children');
501
- },
502
- getSiblings: function ($node) {
503
- return this.getRelatedNodes($node, 'siblings');
504
- },
505
- // find the related nodes
506
- getRelatedNodes: function ($node, relation) {
507
- if (!$node || !($node instanceof $) || !$node.is('.node')) {
508
- return $();
509
- }
510
- if (relation === 'parent') {
511
- return $node.closest('.nodes').siblings('.node');
512
- } else if (relation === 'children') {
513
- return $node.siblings('.nodes').children('.hierarchy').find('.node:first');
514
- } else if (relation === 'siblings') {
515
- return $node.closest('.hierarchy').siblings().find('.node:first');
516
- } else {
517
- return $();
518
- }
519
- },
520
- hideParentEnd: function (event) {
521
- $(event.target).removeClass('sliding');
522
- event.data.parent.addClass('hidden');
523
- },
524
- // recursively hide the ancestor node and sibling nodes of the specified node
525
- hideParent: function ($node) {
526
- var $parent = $node.closest('.nodes').siblings('.node');
527
- if ($parent.find('.spinner').length) {
528
- $node.closest('.orgchart').data('inAjax', false);
529
- }
530
- // hide the sibling nodes
531
- if (this.getNodeState($node, 'siblings').visible) {
532
- this.hideSiblings($node);
533
- }
534
- // hide the lines
535
- $node.parent().addClass('isAncestorsCollapsed');
536
- // hide the superior nodes with transition
537
- if (this.getNodeState($parent).visible) {
538
- $parent.addClass('sliding slide-down').one('transitionend', { 'parent': $parent }, this.hideParentEnd);
539
- }
540
- // if the current node has the parent node, hide it recursively
541
- if (this.getNodeState($parent, 'parent').visible) {
542
- this.hideParent($parent);
543
- }
544
- },
545
- showParentEnd: function (event) {
546
- var $node = event.data.node;
547
- $(event.target).removeClass('sliding');
548
- if (this.isInAction($node)) {
549
- this.switchVerticalArrow($node.children('.topEdge'));
550
- }
551
- },
552
- // show the parent node of the specified node
553
- showParent: function ($node) {
554
- // just show only one superior level
555
- var $parent = $node.closest('.nodes').siblings('.node').removeClass('hidden');
556
- // just show only one line
557
- $node.closest('.hierarchy').removeClass('isAncestorsCollapsed');
558
- // show parent node with animation
559
- this.repaint($parent[0]);
560
- $parent.addClass('sliding').removeClass('slide-down').one('transitionend', { 'node': $node }, this.showParentEnd.bind(this));
561
- },
562
- stopAjax: function ($nodeLevel) {
563
- if ($nodeLevel.find('.spinner').length) {
564
- $nodeLevel.closest('.orgchart').data('inAjax', false);
565
- }
566
- },
567
- isVisibleNode: function (index, elem) {
568
- return this.getNodeState($(elem)).visible;
569
- },
570
- isCompactDescendant: function (index, elem) {
571
- return $(elem).parent().is('.node.compact');
572
- },
573
- // do some necessary cleanup tasks when hide animation is finished
574
- hideChildrenEnd: function (event) {
575
- var $node = event.data.node;
576
- event.data.animatedNodes.removeClass('sliding');
577
- event.data.animatedNodes.closest('.nodes').addClass('hidden');
578
- if (this.isInAction($node)) {
579
- this.switchVerticalArrow($node.children('.bottomEdge'));
580
- }
581
- },
582
- // recursively hide the descendant nodes of the specified node
583
- hideChildren: function ($node) {
584
- $node.closest('.hierarchy').addClass('isChildrenCollapsed');
585
- var $lowerLevel = $node.siblings('.nodes');
586
- this.stopAjax($lowerLevel);
587
- var $animatedNodes = $lowerLevel.find('.node').filter(this.isVisibleNode.bind(this)).not(this.isCompactDescendant.bind(this));
588
- var isVerticalDesc = $lowerLevel.is('.vertical');
589
- if (!isVerticalDesc) {
590
- $animatedNodes.closest('.hierarchy').addClass('isCollapsedDescendant');
591
- }
592
- if ($lowerLevel.is('.vertical') || $lowerLevel.find('.vertical').length) {
593
- $animatedNodes.find(this.options.icons.expanded).removeClass(this.options.icons.expanded).addClass(this.options.icons.collapsed);
594
- }
595
- this.repaint($animatedNodes.get(0));
596
- $animatedNodes.addClass('sliding slide-up').eq(0).one('transitionend', { 'animatedNodes': $animatedNodes, 'lowerLevel': $lowerLevel, 'node': $node }, this.hideChildrenEnd.bind(this));
597
- },
598
- //
599
- showChildrenEnd: function (event) {
600
- var $node = event.data.node;
601
- event.data.animatedNodes.removeClass('sliding');
602
- if (this.isInAction($node)) {
603
- this.switchVerticalArrow($node.children('.bottomEdge'));
604
- }
605
- },
606
- // show the children nodes of the specified node
607
- showChildren: function ($node) {
608
- var that = this;
609
- $node.closest('.hierarchy').removeClass('isChildrenCollapsed');
610
- var $levels = $node.siblings('.nodes');
611
- var isVerticalDesc = $levels.is('.vertical');
612
- var $animatedNodes = isVerticalDesc
613
- ? $levels.removeClass('hidden').find('.node').filter(this.isVisibleNode.bind(this))
614
- : $levels.removeClass('hidden').children('.hierarchy').find('.node:first').filter(this.isVisibleNode.bind(this));
615
- if (!isVerticalDesc) {
616
- $animatedNodes.filter(':not(:only-child)').closest('.hierarchy').addClass('isChildrenCollapsed');
617
- $animatedNodes.closest('.hierarchy').removeClass('isCollapsedDescendant');
618
- }
619
- // the two following statements are used to enforce browser to repaint
620
- this.repaint($animatedNodes.get(0));
621
- $animatedNodes.addClass('sliding').removeClass('slide-up').eq(0).one('transitionend', { 'node': $node, 'animatedNodes': $animatedNodes }, this.showChildrenEnd.bind(this));
622
- },
623
- //
624
- hideSiblingsEnd: function (event) {
625
- var that = this;
626
- var $node = event.data.node;
627
- var $nodeContainer = event.data.nodeContainer;
628
- var direction = event.data.direction;
629
- var $siblings = direction ? (direction === 'left' ? $nodeContainer.prevAll(':not(.hidden)') : $nodeContainer.nextAll(':not(.hidden)')) : $nodeContainer.siblings();
630
- event.data.animatedNodes.removeClass('sliding');
631
- $siblings.find('.node:gt(0)').filter(this.isVisibleNode.bind(this))
632
- .removeClass('slide-left slide-right')
633
- .addClass(function() {
634
- if (that.options.compact) {
635
- return '';
636
- } else {
637
- return 'slide-up';
638
- }
639
- });
640
- $siblings.find('.nodes, .vertical').addClass('hidden')
641
- .end().addClass('hidden');
642
-
643
- if (this.isInAction($node)) {
644
- this.switchHorizontalArrow($node);
645
- }
646
- },
647
- // hide the sibling nodes of the specified node
648
- hideSiblings: function ($node, direction) {
649
- var that = this;
650
- var $nodeContainer = $node.closest('.hierarchy').addClass('isSiblingsCollapsed');
651
- if ($nodeContainer.siblings().find('.spinner').length) {
652
- $node.closest('.orgchart').data('inAjax', false);
653
- }
654
- if (direction) {
655
- if (direction === 'left') {
656
- $nodeContainer.addClass('left-sibs')
657
- .prevAll('.isSiblingsCollapsed').removeClass('isSiblingsCollapsed left-sibs').end()
658
- .prevAll().addClass('isCollapsedSibling isChildrenCollapsed')
659
- .find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-right');
660
- } else {
661
- $nodeContainer.addClass('right-sibs')
662
- .nextAll('.isSiblingsCollapsed').removeClass('isSiblingsCollapsed right-sibs').end()
663
- .nextAll().addClass('isCollapsedSibling isChildrenCollapsed')
664
- .find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-left');
665
- }
666
- } else {
667
- $nodeContainer.prevAll().find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-right');
668
- $nodeContainer.nextAll().find('.node').filter(this.isVisibleNode.bind(this)).addClass('sliding slide-left');
669
- $nodeContainer.siblings().addClass('isCollapsedSibling isChildrenCollapsed');
670
- }
671
- var $animatedNodes = $nodeContainer.siblings().find('.sliding');
672
- $animatedNodes.eq(0).one('transitionend', { 'node': $node, 'nodeContainer': $nodeContainer, 'direction': direction, 'animatedNodes': $animatedNodes }, this.hideSiblingsEnd.bind(this));
673
- },
674
- //
675
- showSiblingsEnd: function (event) {
676
- var $node = event.data.node;
677
- event.data.visibleNodes.removeClass('sliding');
678
- if (this.isInAction($node)) {
679
- this.switchHorizontalArrow($node);
680
- $node.children('.topEdge').removeClass(this.options.icons.expandToUp).addClass(this.options.icons.collapseToDown);
681
- }
682
- },
683
- //
684
- showRelatedParentEnd: function(event) {
685
- $(event.target).removeClass('sliding');
686
- },
687
- // show the sibling nodes of the specified node
688
- showSiblings: function ($node, direction) {
689
- var that = this;
690
- // firstly, show the sibling nodes
691
- var $siblings = $();
692
- var $nodeContainer = $node.closest('.hierarchy');
693
- if (direction) {
694
- if (direction === 'left') {
695
- $siblings = $nodeContainer.prevAll().removeClass('hidden');
696
- } else {
697
- $siblings = $nodeContainer.nextAll().removeClass('hidden');
698
- }
699
- } else {
700
- $siblings = $node.closest('.hierarchy').siblings().removeClass('hidden');
701
- }
702
- // secondly, show the lines
703
- var $upperLevel = $node.closest('.nodes').siblings('.node');
704
- if (direction) {
705
- $nodeContainer.removeClass(direction + '-sibs');
706
- if (!$nodeContainer.is('[class*=-sibs]')) {
707
- $nodeContainer.removeClass('isSiblingsCollapsed');
708
- }
709
- $siblings.removeClass('isCollapsedSibling ' + direction + '-sibs');
710
- } else {
711
- $node.closest('.hierarchy').removeClass('isSiblingsCollapsed');
712
- $siblings.removeClass('isCollapsedSibling');
713
- }
714
- // thirdly, show parent node if it is collapsed
715
- if (!this.getNodeState($node, 'parent').visible) {
716
- $node.closest('.hierarchy').removeClass('isAncestorsCollapsed');
717
- $upperLevel.removeClass('hidden');
718
- this.repaint($upperLevel[0]);
719
- $upperLevel.addClass('sliding').removeClass('slide-down').one('transitionend', this.showRelatedParentEnd);
720
- }
721
- // lastly, show the sibling nodes with animation
722
- var $visibleNodes = $siblings.find('.node').filter(this.isVisibleNode.bind(this));
723
- this.repaint($visibleNodes.get(0));
724
- $visibleNodes.addClass('sliding').removeClass('slide-left slide-right');
725
- $visibleNodes.eq(0).one('transitionend', { 'node': $node, 'visibleNodes': $visibleNodes }, this.showSiblingsEnd.bind(this));
726
- },
727
- // start up loading status for requesting new nodes
728
- startLoading: function ($edge) {
729
- var $chart = this.$chart;
730
- if (typeof $chart.data('inAjax') !== 'undefined' && $chart.data('inAjax') === true) {
731
- return false;
732
- }
733
-
734
- $edge.addClass('hidden');
735
- $edge.parent().append(`<i class="${this.options.icons.theme} ${this.options.icons.spinner} spinner"></i>`)
736
- .children().not('.spinner').css('opacity', 0.2);
737
- $chart.data('inAjax', true);
738
- $('.oc-export-btn').prop('disabled', true);
739
- return true;
740
- },
741
- // terminate loading status for requesting new nodes
742
- endLoading: function ($edge) {
743
- var $node = $edge.parent();
744
- $edge.removeClass('hidden');
745
- $node.find('.spinner').remove();
746
- $node.children().removeAttr('style');
747
- this.$chart.data('inAjax', false);
748
- $('.oc-export-btn').prop('disabled', false);
749
- },
750
- // whether the cursor is hovering over the node
751
- isInAction: function ($node) {
752
- return [
753
- this.options.icons.expandToUp,
754
- this.options.icons.collapseToDown,
755
- this.options.icons.collapseToLeft,
756
- this.options.icons.expandToRight
757
- ].some((icon) => $node.children('.edge').attr('class').indexOf(icon) > -1);
758
- },
759
- //
760
- switchVerticalArrow: function ($arrow) {
761
- $arrow.toggleClass(`${this.options.icons.expandToUp} ${this.options.icons.collapseToDown}`);
762
- },
763
- //
764
- switchHorizontalArrow: function ($node) {
765
- var opts = this.options;
766
- if (opts.toggleSiblingsResp && (typeof opts.ajaxURL === 'undefined' || $node.closest('.nodes').data('siblingsLoaded'))) {
767
- var $prevSib = $node.parent().prev();
768
- if ($prevSib.length) {
769
- if ($prevSib.is('.hidden')) {
770
- $node.children('.leftEdge').addClass(opts.icons.collapseToLeft).removeClass(opts.icons.expandToRight);
771
- } else {
772
- $node.children('.leftEdge').addClass(opts.icons.expandToRight).removeClass(opts.icons.collapseToLeft);
773
- }
774
- }
775
- var $nextSib = $node.parent().next();
776
- if ($nextSib.length) {
777
- if ($nextSib.is('.hidden')) {
778
- $node.children('.rightEdge').addClass(opts.icons.expandToRight).removeClass(opts.icons.collapseToLeft);
779
- } else {
780
- $node.children('.rightEdge').addClass(opts.icons.collapseToLeft).removeClass(opts.icons.expandToRight);
781
- }
782
- }
783
- } else {
784
- var $sibs = $node.parent().siblings();
785
- var sibsVisible = $sibs.length ? !$sibs.is('.hidden') : false;
786
- $node.children('.leftEdge').toggleClass(opts.icons.expandToRight, sibsVisible).toggleClass(opts.icons.collapseToLeft, !sibsVisible);
787
- $node.children('.rightEdge').toggleClass(opts.icons.collapseToLeft, sibsVisible).toggleClass(opts.icons.expandToRight, !sibsVisible);
788
- }
789
- },
790
- //
791
- repaint: function (node) {
792
- if (node) {
793
- node.style.offsetWidth = node.offsetWidth;
794
- }
795
- },
796
- // determines how to show arrow buttons
797
- nodeEnterLeaveHandler: function (event) {
798
- var $node = $(event.delegateTarget);
799
- var flag = false;
800
- if ($node.closest('.nodes.vertical').length) {
801
- var $toggleBtn = $node.children('.toggleBtn');
802
- if (event.type === 'mouseenter') {
803
- if ($node.children('.toggleBtn').length) {
804
- flag = this.getNodeState($node, 'children').visible;
805
- $toggleBtn.toggleClass(this.options.icons.collapsed, !flag).toggleClass(this.options.icons.expanded, flag);
806
- }
807
- } else {
808
- $toggleBtn.removeClass(`${this.options.icons.collapsed} ${this.options.icons.expanded}`);
809
- }
810
- } else {
811
- var $topEdge = $node.children('.topEdge');
812
- var $rightEdge = $node.children('.rightEdge');
813
- var $bottomEdge = $node.children('.bottomEdge');
814
- var $leftEdge = $node.children('.leftEdge');
815
- if (event.type === 'mouseenter') {
816
- if ($topEdge.length) {
817
- flag = this.getNodeState($node, 'parent').visible;
818
- $topEdge.toggleClass(this.options.icons.expandToUp, !flag).toggleClass(this.options.icons.collapseToDown, flag);
819
- }
820
- if ($bottomEdge.length) {
821
- flag = this.getNodeState($node, 'children').visible;
822
- $bottomEdge.toggleClass(this.options.icons.collapseToDown, !flag).toggleClass(this.options.icons.expandToUp, flag);
823
- }
824
- if ($leftEdge.length) {
825
- this.switchHorizontalArrow($node);
826
- }
827
- } else {
828
- $node.children('.edge').removeClass(`${this.options.icons.expandToUp} ${this.options.icons.collapseToDown} ${this.options.icons.collapseToLeft} ${this.options.icons.expandToRight}`);
829
- }
830
- }
831
- },
832
- //
833
- nodeClickHandler: function (event) {
834
- this.$chart.find('.focused').removeClass('focused');
835
- $(event.delegateTarget).addClass('focused');
836
- },
837
- addAncestors: function (data, parentId) {
838
- var $root = this.$chart.children('.nodes').children('.hierarchy');
839
- this.buildHierarchy($root, data);
840
- $root.children().slice(0, 2)
841
- .wrapAll('<li class="hierarchy"></li>').parent()
842
- .appendTo($('#' + parentId).siblings('.nodes'));
843
- },
844
- addDescendants:function (data, $parent) {
845
- var that = this;
846
- var $descendants = $('<ul class="nodes"></ul>');
847
- $parent.after($descendants);
848
- $.each(data, function (i) {
849
- $descendants.append($('<li class="hierarchy"></li>'));
850
- that.buildHierarchy($descendants.children().eq(i), this);
851
- });
852
- },
853
- //
854
- HideFirstParentEnd: function (event) {
855
- var $topEdge = event.data.topEdge;
856
- var $node = $topEdge.parent();
857
- if (this.isInAction($node)) {
858
- this.switchVerticalArrow($topEdge);
859
- this.switchHorizontalArrow($node);
860
- }
861
- },
862
- // actions on clinking top edge of a node
863
- topEdgeClickHandler: function (event) {
864
- var that = this;
865
- var $topEdge = $(event.target);
866
- var $node = $(event.delegateTarget);
867
- var parentState = this.getNodeState($node, 'parent');
868
- if (parentState.exist) {
869
- var $parent = $node.closest('.nodes').siblings('.node');
870
- if ($parent.is('.sliding')) { return; }
871
- // hide the ancestor nodes and sibling nodes of the specified node
872
- if (parentState.visible) {
873
- this.hideParent($node);
874
- $parent.one('transitionend', { 'topEdge': $topEdge }, this.HideFirstParentEnd.bind(this));
875
- this.triggerHideEvent($node, 'parent');
876
- } else { // show the ancestors and siblings
877
- this.showParent($node);
878
- this.triggerShowEvent($node, 'parent');
879
- }
880
- }
881
- },
882
- // actions on clinking bottom edge of a node
883
- bottomEdgeClickHandler: function (event) {
884
- var $bottomEdge = $(event.target);
885
- var $node = $(event.delegateTarget);
886
- var childrenState = this.getNodeState($node, 'children');
887
- if (childrenState.exist) {
888
- var $children = $node.siblings('.nodes').children().children('.node');
889
- if ($children.is('.sliding')) { return; }
890
- // hide the descendant nodes of the specified node
891
- if (childrenState.visible) {
892
- this.hideChildren($node);
893
- this.triggerHideEvent($node, 'children');
894
- } else { // show the descendants
895
- this.showChildren($node);
896
- this.triggerShowEvent($node, 'children');
897
- }
898
- }
899
- },
900
- // actions on clicking horizontal edges
901
- hEdgeClickHandler: function (event) {
902
- var $hEdge = $(event.target);
903
- var $node = $(event.delegateTarget);
904
- var opts = this.options;
905
- var siblingsState = this.getNodeState($node, 'siblings');
906
- if (siblingsState.exist) {
907
- var $siblings = $node.closest('.hierarchy').siblings();
908
- if ($siblings.find('.sliding').length) { return; }
909
- if (opts.toggleSiblingsResp) {
910
- var $prevSib = $node.closest('.hierarchy').prev();
911
- var $nextSib = $node.closest('.hierarchy').next();
912
- if ($hEdge.is('.leftEdge')) {
913
- if ($prevSib.is('.hidden')) {
914
- this.showSiblings($node, 'left');
915
- this.triggerShowEvent($node,'siblings');
916
- } else {
917
- this.hideSiblings($node, 'left');
918
- this.triggerHideEvent($node, 'siblings');
919
- }
920
- } else {
921
- if ($nextSib.is('.hidden')) {
922
- this.showSiblings($node, 'right');
923
- this.triggerShowEvent($node,'siblings');
924
- } else {
925
- this.hideSiblings($node, 'right');
926
- this.triggerHideEvent($node, 'siblings');
927
- }
928
- }
929
- } else {
930
- if (siblingsState.visible) {
931
- this.hideSiblings($node);
932
- this.triggerHideEvent($node, 'siblings');
933
- } else {
934
- this.showSiblings($node);
935
- this.triggerShowEvent($node, 'siblings');
936
- }
937
- }
938
- }
939
- },
940
- // show the compact node's children in the compact mode
941
- backToCompactHandler: function (event) {
942
- $(event.delegateTarget).removeClass('looseMode')
943
- .find('.looseMode').removeClass('looseMode')
944
- .children('.backToCompactSymbol').addClass('hidden').end()
945
- .children('.backToLooseSymbol').removeClass('hidden');
946
- $(event.delegateTarget).children('.backToCompactSymbol').addClass('hidden').end()
947
- .children('.backToLooseSymbol').removeClass('hidden');
948
- },
949
- // show the compact node's children in the loose mode
950
- backToLooseHandler: function (event) {
951
- $(event.delegateTarget)
952
- .addClass('looseMode')
953
- .children('.backToLooseSymbol').addClass('hidden').end()
954
- .children('.backToCompactSymbol').removeClass('hidden');
955
- },
956
- //
957
- expandVNodesEnd: function (event) {
958
- event.data.vNodes.removeClass('sliding');
959
- },
960
- //
961
- collapseVNodesEnd: function (event) {
962
- event.data.vNodes.removeClass('sliding').closest('ul').addClass('hidden');
963
- },
964
- // event handler for toggle buttons in Hybrid(horizontal + vertical) OrgChart
965
- toggleVNodes: function (event) {
966
- var $toggleBtn = $(event.target);
967
- var $descWrapper = $toggleBtn.parent().next();
968
- var $descendants = $descWrapper.find('.node');
969
- var $children = $descWrapper.children().children('.node');
970
- if ($children.is('.sliding')) { return; }
971
- $toggleBtn.toggleClass(`${this.options.icons.collapsed} ${this.options.icons.expanded}`);
972
- if ($descendants.eq(0).is('.slide-up')) {
973
- $descWrapper.removeClass('hidden');
974
- this.repaint($children.get(0));
975
- $children.addClass('sliding').removeClass('slide-up').eq(0).one('transitionend', { 'vNodes': $children }, this.expandVNodesEnd);
976
- } else {
977
- $descendants.addClass('sliding slide-up').eq(0).one('transitionend', { 'vNodes': $descendants }, this.collapseVNodesEnd);
978
- $descendants.find('.toggleBtn').removeClass(`${this.options.icons.collapsed} ${this.options.icons.expanded}`);
979
- }
980
- },
981
- //
982
- createGhostNode: function (event) {
983
- var $nodeDiv = $(event.target);
984
- var opts = this.options;
985
- var origEvent = event.originalEvent;
986
- var isFirefox = /firefox/.test(window.navigator.userAgent.toLowerCase());
987
- var ghostNode, nodeCover;
988
- if (!document.querySelector('.ghost-node')) {
989
- ghostNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
990
- if (!ghostNode.classList) return;
991
- ghostNode.classList.add('ghost-node');
992
- nodeCover = document.createElementNS('http://www.w3.org/2000/svg','rect');
993
- ghostNode.appendChild(nodeCover);
994
- $nodeDiv.closest('.orgchart').append(ghostNode);
995
- } else {
996
- ghostNode = $nodeDiv.closest('.orgchart').children('.ghost-node').get(0);
997
- nodeCover = $(ghostNode).children().get(0);
998
- }
999
- var transValues = $nodeDiv.closest('.orgchart').css('transform').split(',');
1000
- var isHorizontal = opts.direction === 't2b' || opts.direction === 'b2t';
1001
- var scale = Math.abs(window.parseFloat(isHorizontal ? transValues[0].slice(transValues[0].indexOf('(') + 1) : transValues[1]));
1002
- ghostNode.setAttribute('width', isHorizontal ? $nodeDiv.outerWidth(false) : $nodeDiv.outerHeight(false));
1003
- ghostNode.setAttribute('height', isHorizontal ? $nodeDiv.outerHeight(false) : $nodeDiv.outerWidth(false));
1004
- nodeCover.setAttribute('x',5 * scale);
1005
- nodeCover.setAttribute('y',5 * scale);
1006
- nodeCover.setAttribute('width', 120 * scale);
1007
- nodeCover.setAttribute('height', 40 * scale);
1008
- nodeCover.setAttribute('rx', 4 * scale);
1009
- nodeCover.setAttribute('ry', 4 * scale);
1010
- nodeCover.setAttribute('stroke-width', 1 * scale);
1011
- var xOffset = origEvent.offsetX * scale;
1012
- var yOffset = origEvent.offsetY * scale;
1013
- if (opts.direction === 'l2r') {
1014
- xOffset = origEvent.offsetY * scale;
1015
- yOffset = origEvent.offsetX * scale;
1016
- } else if (opts.direction === 'r2l') {
1017
- xOffset = $nodeDiv.outerWidth(false) - origEvent.offsetY * scale;
1018
- yOffset = origEvent.offsetX * scale;
1019
- } else if (opts.direction === 'b2t') {
1020
- xOffset = $nodeDiv.outerWidth(false) - origEvent.offsetX * scale;
1021
- yOffset = $nodeDiv.outerHeight(false) - origEvent.offsetY * scale;
1022
- }
1023
- if (isFirefox) { // hack for old version of Firefox(< 48.0)
1024
- nodeCover.setAttribute('fill', 'rgb(255, 255, 255)');
1025
- nodeCover.setAttribute('stroke', 'rgb(191, 0, 0)');
1026
- var ghostNodeWrapper = document.createElement('img');
1027
- ghostNodeWrapper.src = 'data:image/svg+xml;utf8,' + (new XMLSerializer()).serializeToString(ghostNode);
1028
- origEvent.dataTransfer.setDragImage(ghostNodeWrapper, xOffset, yOffset);
1029
- } else {
1030
- // IE/Edge do not support this, so only use it if we can
1031
- if (origEvent.dataTransfer.setDragImage)
1032
- origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset);
1033
- }
1034
- },
1035
- // get the level amount of a hierachy
1036
- getUpperLevel: function ($node) {
1037
- if (!$node.is('.node')) {
1038
- return 0;
1039
- }
1040
- return $node.parents('.hierarchy').length;
1041
- },
1042
- // get the level amount of a hierachy
1043
- getLowerLevel: function ($node) {
1044
- if (!$node.is('.node')) {
1045
- return 0;
1046
- }
1047
- return $node.closest('.hierarchy').find('.nodes').length + 1;
1048
- },
1049
- // get nodes in level order traversal
1050
- getLevelOrderNodes: function ($root) {
1051
- if(!$root) return [];
1052
- var queue = [];
1053
- var output = [];
1054
- queue.push($root);
1055
- while(queue.length) {
1056
- var row = [];
1057
- for(var i = 0; i < queue.length; i++) {
1058
- var cur = queue.shift();
1059
- var children = this.getChildren(cur);
1060
- if(children.length) {
1061
- queue.push(children.toArray().flat());
1062
- }
1063
- row.push($(cur));
1064
- }
1065
- output.push(row);
1066
- }
1067
- return output;
1068
- },
1069
- //
1070
- filterAllowedDropNodes: function ($dragged) {
1071
- var opts = this.options;
1072
- // what is being dragged? a node, or something within a node?
1073
- var draggingNode = $dragged.closest('[draggable]').hasClass('node');
1074
- var $dragZone = $dragged.closest('.nodes').siblings('.node'); // parent node
1075
- var $dragHier = $dragged.closest('.hierarchy').find('.node'); // this node, and its children
1076
- this.$chart.data('dragged', $dragged)
1077
- .find('.node').each(function (index, node) {
1078
- if (!draggingNode || $dragHier.index(node) === -1) {
1079
- if (opts.dropCriteria) {
1080
- if (opts.dropCriteria($dragged, $dragZone, $(node))) {
1081
- $(node).addClass('allowedDrop');
1082
- }
1083
- } else {
1084
- $(node).addClass('allowedDrop');
1085
- }
1086
- }
1087
- });
1088
- },
1089
- //
1090
- dragstartHandler: function (event) {
1091
- event.originalEvent.dataTransfer.setData('text/html', 'hack for firefox');
1092
- // if users enable zoom or direction options
1093
- if (this.$chart.css('transform') !== 'none') {
1094
- this.createGhostNode(event);
1095
- }
1096
- this.filterAllowedDropNodes($(event.target));
1097
- },
1098
- //
1099
- dragoverHandler: function (event) {
1100
- if (!$(event.delegateTarget).is('.allowedDrop')) {
1101
- event.originalEvent.dataTransfer.dropEffect = 'none';
1102
- } else {
1103
- // default action for drag-and-drop of div is not to drop, so preventing default action for nodes which have allowedDrop class
1104
- //to fix drag and drop on IE and Edge
1105
- event.preventDefault();
1106
- }
1107
- },
1108
- //
1109
- dragendHandler: function (event) {
1110
- this.$chart.find('.allowedDrop').removeClass('allowedDrop');
1111
- },
1112
- // when user drops the node, it will be removed from original parent node and be added to new parent node
1113
- dropHandler: async function (event) {
1114
- var that = this;
1115
- var $dropZone = $(event.delegateTarget);
1116
- var $dragged = this.$chart.data('dragged');
1117
-
1118
- // Pass on drops which are not nodes (since they are not our doing)
1119
- if (!$dragged.hasClass('node')) {
1120
- this.$chart.triggerHandler({ 'type': 'otherdropped.orgchart', 'draggedItem': $dragged, 'dropZone': $dropZone });
1121
- return;
1122
- }
1123
-
1124
- if (!$dropZone.hasClass('allowedDrop')) {
1125
- // We are trying to drop a node into a node which isn't allowed
1126
- // IE/Edge have a habit of allowing this, so we need our own double-check
1127
- return;
1128
- }
1129
-
1130
- var $dragZone = $dragged.closest('.nodes').siblings('.node');
1131
- var dropEvent = $.Event('nodedrop.orgchart');
1132
- this.$chart.trigger(dropEvent, { 'draggedNode': $dragged, 'dragZone': $dragZone, 'dropZone': $dropZone });
1133
- if (dropEvent.isDefaultPrevented()) {
1134
- return;
1135
- }
1136
- // special process for hybrid chart
1137
- var datasource = this.$chart.data('options').data;
1138
- var digger = new JSONDigger(datasource, this.$chart.data('options').nodeId, 'children');
1139
- const hybridNode = digger.findOneNode({ 'hybrid': true });
1140
- if (this.$chart.data('options').verticalLevel > 1 || hybridNode) {
1141
- var draggedNode = digger.findNodeById($dragged.data('nodeData').id);
1142
- var copy = Object.assign({}, draggedNode);
1143
- digger.removeNode(draggedNode.id);
1144
- var dropNode = digger.findNodeById($dropZone.data('nodeData').id);
1145
- if (dropNode.children) {
1146
- dropNode.children.push(copy);
1147
- } else {
1148
- dropNode.children = [copy];
1149
- }
1150
- that.init({ 'data': datasource });
1151
- } else {
1152
- // The folowing code snippets are used to process horizontal chart
1153
- // firstly, deal with the hierarchy of drop zone
1154
- if (!$dropZone.siblings('.nodes').length) { // if the drop zone is a leaf node
1155
- $dropZone.append(`<i class="edge verticalEdge bottomEdge ${this.options.icons.theme}"></i>`)
1156
- .after('<ul class="nodes"></ul>')
1157
- .siblings('.nodes').append($dragged.find('.horizontalEdge').remove().end().closest('.hierarchy'));
1158
- if ($dropZone.children('.title').length) {
1159
- $dropZone.children('.title').prepend(`<i class="${this.options.icons.theme} ${this.$chart.data('options').icons.parentNode} parentNodeSymbol"></i>`);
1160
- }
1161
- } else {
1162
- var horizontalEdges = `<i class="edge horizontalEdge rightEdge ${this.options.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${this.options.icons.theme}"></i>`;
1163
- if (!$dragged.find('.horizontalEdge').length) {
1164
- $dragged.append(horizontalEdges);
1165
- }
1166
- $dropZone.siblings('.nodes').append($dragged.closest('.hierarchy'));
1167
- var $dropSibs = $dragged.closest('.hierarchy').siblings().find('.node:first');
1168
- if ($dropSibs.length === 1) {
1169
- $dropSibs.append(horizontalEdges);
1170
- }
1171
- }
1172
- // secondly, deal with the hierarchy of dragged node
1173
- if ($dragZone.siblings('.nodes').children('.hierarchy').length === 1) { // if there is only one sibling node left
1174
- $dragZone.siblings('.nodes').children('.hierarchy').find('.node:first')
1175
- .find('.horizontalEdge').remove();
1176
- } else if ($dragZone.siblings('.nodes').children('.hierarchy').length === 0) {
1177
- $dragZone.find('.bottomEdge, .parentNodeSymbol').remove()
1178
- .end().siblings('.nodes').remove();
1179
- }
1180
- }
1181
- },
1182
- //
1183
- touchstartHandler: function (event) {
1184
- if (this.touchHandled)
1185
- return;
1186
-
1187
- if (event.touches && event.touches.length > 1)
1188
- return;
1189
-
1190
- this.touchHandled = true;
1191
- this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch
1192
- event.preventDefault();
1193
- },
1194
- //
1195
- touchmoveHandler: function (event) {
1196
- if (!this.touchHandled)
1197
- return;
1198
-
1199
- if (event.touches && event.touches.length > 1)
1200
- return;
1201
-
1202
- event.preventDefault();
1203
-
1204
- if (!this.touchMoved) {
1205
- // we do not bother with createGhostNode (dragstart does) since the touch event does not have a dataTransfer property
1206
- this.filterAllowedDropNodes($(event.currentTarget)); // will also set 'this.$chart.data('dragged')' for us
1207
- // create an image which can be used to illustrate the drag (our own createGhostNode)
1208
- this.touchDragImage = this.createDragImage(event, this.$chart.data('dragged')[0]);
1209
- }
1210
- this.touchMoved = true;
1211
-
1212
- // move our dragimage so it follows our finger
1213
- this.moveDragImage(event, this.touchDragImage);
1214
-
1215
- var $touching = $(document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY));
1216
- var $touchingNodes = $touching.closest('div.node');
1217
- if ($touchingNodes.length > 0) {
1218
- var touchingNodeElement = $touchingNodes[0];
1219
- if ($touchingNodes.is('.allowedDrop')) {
1220
- this.touchTargetNode = touchingNodeElement;
1221
- }
1222
- else {
1223
- this.touchTargetNode = null;
1224
- }
1225
- }
1226
- else {
1227
- this.touchTargetNode = null;
1228
- }
1229
- },
1230
- //
1231
- touchendHandler: function (event) {
1232
- if (!this.touchHandled) {
1233
- return;
1234
- }
1235
- this.destroyDragImage();
1236
- if (this.touchMoved) {
1237
- // we've had movement, so this was a 'drag' touch
1238
- if (this.touchTargetNode) {
1239
- var fakeEventForDropHandler = { delegateTarget: this.touchTargetNode };
1240
- this.dropHandler(fakeEventForDropHandler);
1241
- this.touchTargetNode = null;
1242
- }
1243
- this.dragendHandler(event);
1244
- }
1245
- else {
1246
- // we did not move, so this was a 'press' touch (fake a click)
1247
- var firstTouch = event.changedTouches[0];
1248
- var fakeMouseClickEvent = document.createEvent('MouseEvents');
1249
- fakeMouseClickEvent.initMouseEvent('click', true, true, window, 1, firstTouch.screenX, firstTouch.screenY, firstTouch.clientX, firstTouch.clientY, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, 0, null);
1250
- event.target.dispatchEvent(fakeMouseClickEvent);
1251
- }
1252
- this.touchHandled = false;
1253
- },
1254
- //
1255
- createDragImage: function (event, source) {
1256
- var dragImage = source.cloneNode(true);
1257
- this.copyStyle(source, dragImage);
1258
- dragImage.style.top = dragImage.style.left = '-9999px';
1259
- var sourceRectangle = source.getBoundingClientRect();
1260
- var sourcePoint = this.getTouchPoint(event);
1261
- this.touchDragImageOffset = { x: sourcePoint.x - sourceRectangle.left, y: sourcePoint.y - sourceRectangle.top };
1262
- dragImage.style.opacity = '0.5';
1263
- document.body.appendChild(dragImage);
1264
- return dragImage;
1265
- },
1266
- //
1267
- destroyDragImage: function () {
1268
- if (this.touchDragImage && this.touchDragImage.parentElement)
1269
- this.touchDragImage.parentElement.removeChild(this.touchDragImage);
1270
- this.touchDragImageOffset = null;
1271
- this.touchDragImage = null;
1272
- },
1273
- //
1274
- copyStyle: function (src, dst) {
1275
- // remove potentially troublesome attributes
1276
- var badAttributes = ['id', 'class', 'style', 'draggable'];
1277
- badAttributes.forEach(function (att) {
1278
- dst.removeAttribute(att);
1279
- });
1280
- // copy canvas content
1281
- if (src instanceof HTMLCanvasElement) {
1282
- var cSrc = src, cDst = dst;
1283
- cDst.width = cSrc.width;
1284
- cDst.height = cSrc.height;
1285
- cDst.getContext('2d').drawImage(cSrc, 0, 0);
1286
- }
1287
- // copy style (without transitions)
1288
- var cs = getComputedStyle(src);
1289
- for (var i = 0; i < cs.length; i++) {
1290
- var key = cs[i];
1291
- if (key.indexOf('transition') < 0) {
1292
- dst.style[key] = cs[key];
1293
- }
1294
- }
1295
- dst.style.pointerEvents = 'none';
1296
- // and repeat for all children
1297
- for (var i = 0; i < src.children.length; i++) {
1298
- this.copyStyle(src.children[i], dst.children[i]);
1299
- }
1300
- },
1301
- //
1302
- getTouchPoint: function (event) {
1303
- if (event && event.touches) {
1304
- event = event.touches[0];
1305
- }
1306
- return {
1307
- x: event.clientX,
1308
- y: event.clientY
1309
- };
1310
- },
1311
- //
1312
- moveDragImage: function (event, image) {
1313
- if (!event || !image)
1314
- return;
1315
- var orgChartMaster = this;
1316
- requestAnimationFrame(function () {
1317
- var pt = orgChartMaster.getTouchPoint(event);
1318
- var s = image.style;
1319
- s.position = 'absolute';
1320
- s.pointerEvents = 'none';
1321
- s.zIndex = '999999';
1322
- if (orgChartMaster.touchDragImageOffset) {
1323
- s.left = Math.round(pt.x - orgChartMaster.touchDragImageOffset.x) + 'px';
1324
- s.top = Math.round(pt.y - orgChartMaster.touchDragImageOffset.y) + 'px';
1325
- }
1326
- });
1327
- },
1328
- //
1329
- bindDragDrop: function ($node) {
1330
- $node.on('dragstart', this.dragstartHandler.bind(this))
1331
- .on('dragover', this.dragoverHandler.bind(this))
1332
- .on('dragend', this.dragendHandler.bind(this))
1333
- .on('drop', this.dropHandler.bind(this))
1334
- .on('touchstart', this.touchstartHandler.bind(this))
1335
- .on('touchmove', this.touchmoveHandler.bind(this))
1336
- .on('touchend', this.touchendHandler.bind(this));
1337
- },
1338
- // create node
1339
- createNode: function (data) {
1340
- var that = this;
1341
- var opts = this.options;
1342
- var level = data.level;
1343
- if (data.children && data[opts.nodeId]) {
1344
- $.each(data.children, function (index, child) {
1345
- child.parentId = data[opts.nodeId]
1346
- });
1347
- }
1348
- // construct the content of node
1349
- var $nodeDiv = $('<div' + (opts.draggable ? ' draggable="true"' : '') + (data[opts.nodeId] ? ' id="' + data[opts.nodeId] + '"' : '') + (data.parentId ? ' data-parent="' + data.parentId + '"' : '') + '>')
1350
- .addClass('node ' + (data.className || '') + (level > opts.visibleLevel ? ' slide-up' : ''));
1351
- if (opts.nodeTemplate) {
1352
- $nodeDiv.append(opts.nodeTemplate(data));
1353
- } else {
1354
- $nodeDiv.append('<div class="title">' + data[opts.nodeTitle] + '</div>')
1355
- .append(typeof opts.nodeContent !== 'undefined' ? '<div class="content">' + (data[opts.nodeContent] || '') + '</div>' : '');
1356
- }
1357
- //
1358
- var nodeData = $.extend({}, data);
1359
- delete nodeData.children;
1360
- $nodeDiv.data('nodeData', nodeData);
1361
- // append 4 direction arrows or expand/collapse buttons or reset buttons
1362
- var flags = data.relationship || '';
1363
- if ((opts.verticalLevel && level >= opts.verticalLevel) || data.vertical) {
1364
- if (Number(flags.substr(2,1))) {
1365
- $nodeDiv.append(`<i class="toggleBtn ${opts.icons.theme}"></i>`)
1366
- .children('.title').prepend(`<i class="${opts.icons.theme} ${opts.icons.parentNode} parentNodeSymbol"></i>`);
1367
- }
1368
- } else if (data.hybrid) {
1369
- if (Number(flags.substr(2,1))) {
1370
- $nodeDiv.append(`<i class="edge verticalEdge bottomEdge ${opts.icons.theme}"></i>`)
1371
- .children('.title').prepend(`<i class="${opts.icons.theme} ${opts.icons.parentNode} parentNodeSymbol"></i>`);
1372
- }
1373
- } else if (data.compact) {
1374
- $nodeDiv.css('grid-template-columns', `repeat(${Math.floor(Math.sqrt(data.children.length + 1))}, auto)`);
1375
- if (Number(flags.substr(2,1))) {
1376
- $nodeDiv.append(`
1377
- <i class="${opts.icons.theme} ${opts.icons.backToCompact} backToCompactSymbol hidden"></i>
1378
- <i class="${opts.icons.theme} ${opts.icons.backToLoose} backToLooseSymbol"></i>
1379
- `)
1380
- .children('.title').prepend(`<i class="${opts.icons.theme} ${opts.icons.parentNode} parentNodeSymbol"></i>`);
1381
- }
1382
- } else if (data.associatedCompact) {
1383
-
1384
- } else {
1385
- if (Number(flags.substr(0,1))) {
1386
- $nodeDiv.append(`<i class="edge verticalEdge topEdge ${opts.icons.theme}"></i>`);
1387
- }
1388
- if(Number(flags.substr(1,1))) {
1389
- $nodeDiv.append(`<i class="edge horizontalEdge rightEdge ${opts.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${opts.icons.theme}"></i>`);
1390
- }
1391
- if(Number(flags.substr(2,1))) {
1392
- $nodeDiv.append(`<i class="edge verticalEdge bottomEdge ${opts.icons.theme}"></i>`)
1393
- .children('.title').prepend(`<i class="${opts.icons.theme} ${opts.icons.parentNode} parentNodeSymbol"></i>`);
1394
- }
1395
- }
1396
-
1397
- $nodeDiv.on('mouseenter mouseleave', this.nodeEnterLeaveHandler.bind(this));
1398
- $nodeDiv.on('click', this.nodeClickHandler.bind(this));
1399
- $nodeDiv.on('click', '.topEdge', this.topEdgeClickHandler.bind(this));
1400
- $nodeDiv.on('click', '.bottomEdge', this.bottomEdgeClickHandler.bind(this));
1401
- $nodeDiv.on('click', '.leftEdge, .rightEdge', this.hEdgeClickHandler.bind(this));
1402
- $nodeDiv.on('click', '.toggleBtn', this.toggleVNodes.bind(this));
1403
- $nodeDiv.on('click', '> .backToCompactSymbol',this.backToCompactHandler.bind(this));
1404
- $nodeDiv.on('click', '> .backToLooseSymbol',this.backToLooseHandler.bind(this));
1405
-
1406
- if (opts.draggable) {
1407
- this.bindDragDrop($nodeDiv);
1408
- this.touchHandled = false;
1409
- this.touchMoved = false;
1410
- this.touchTargetNode = null;
1411
- }
1412
- // allow user to append dom modification after finishing node create of orgchart
1413
- if (opts.createNode) {
1414
- opts.createNode($nodeDiv, data);
1415
- }
1416
-
1417
- return $nodeDiv;
1418
- },
1419
- // recursively build the tree
1420
- buildHierarchy: function ($appendTo, data) {
1421
- var that = this;
1422
- var opts = this.options;
1423
- var level = 0;
1424
- var $nodeDiv;
1425
- if (data.level) {
1426
- level = data.level;
1427
- } else {
1428
- level = data.level = $appendTo.parentsUntil('.orgchart', '.nodes').length;
1429
- }
1430
- // Construct the node
1431
- if (Object.keys(data).length > 2) {
1432
- $nodeDiv = this.createNode(data);
1433
- $appendTo.append($nodeDiv);
1434
- }
1435
- // Construct the "inferior nodes"
1436
- if (data.children && data.children.length) {
1437
- var isHidden = level + 1 > opts.visibleLevel || (data.collapsed !== undefined && data.collapsed);
1438
- var $nodesLayer;
1439
- if ((opts.verticalLevel && (level + 1) >= opts.verticalLevel) || data.hybrid) {
1440
- $nodesLayer = $('<ul class="nodes">');
1441
- if (isHidden && (opts.verticalLevel && (level + 1) >= opts.verticalLevel)) {
1442
- $nodesLayer.addClass('hidden');
1443
- }
1444
- if (((opts.verticalLevel && level + 1 === opts.verticalLevel) || data.hybrid)
1445
- && !$appendTo.closest('.vertical').length) {
1446
- $appendTo.append($nodesLayer.addClass('vertical'));
1447
- } else {
1448
- $appendTo.append($nodesLayer);
1449
- }
1450
- } else if (data.compact) {
1451
- $nodeDiv.addClass('compact');
1452
- } else {
1453
- $nodesLayer = $('<ul class="nodes' + (isHidden ? ' hidden' : '') + '">');
1454
- if (Object.keys(data).length === 2) {
1455
- $appendTo.append($nodesLayer);
1456
- } else {
1457
- if (isHidden) {
1458
- $appendTo.addClass('isChildrenCollapsed');
1459
- }
1460
- $appendTo.append($nodesLayer);
1461
- }
1462
- }
1463
- // recurse through children nodes
1464
- $.each(data.children, function () {
1465
- this.level = level + 1;
1466
- if (data.compact) {
1467
- that.buildHierarchy($nodeDiv, this);
1468
- } else {
1469
- var $nodeCell = $('<li class="hierarchy">');
1470
- $nodesLayer.append($nodeCell);
1471
- that.buildHierarchy($nodeCell, this);
1472
- }
1473
- });
1474
- }
1475
- },
1476
- // build the child nodes of specific node
1477
- buildChildNode: function ($appendTo, data) {
1478
- this.buildHierarchy($appendTo, { 'children': data });
1479
- },
1480
- // exposed method
1481
- addChildren: function ($node, data) {
1482
- this.buildChildNode($node.closest('.hierarchy'), data);
1483
- if (!$node.find('.parentNodeSymbol').length) {
1484
- $node.children('.title').prepend(`<i class="${this.options.icons.theme} ${this.options.icons.parentNode} parentNodeSymbol"></i>`);
1485
- }
1486
- if ($node.closest('.nodes.vertical').length) {
1487
- if (!$node.children('.toggleBtn').length) {
1488
- $node.append(`<i class="toggleBtn ${this.options.icons.theme}"></i>`);
1489
- }
1490
- } else {
1491
- if (!$node.children('.bottomEdge').length) {
1492
- $node.append(`<i class="edge verticalEdge bottomEdge ${this.options.icons.theme}"></i>`);
1493
- }
1494
- }
1495
- if (this.isInAction($node)) {
1496
- this.switchVerticalArrow($node.children('.bottomEdge'));
1497
- }
1498
- },
1499
- // build the parent node of specific node
1500
- buildParentNode: function ($currentRoot, data) {
1501
- data.relationship = data.relationship || '001';
1502
- var $newRootWrapper = $('<ul class="nodes"><li class="hierarchy"></li></ul>')
1503
- .find('.hierarchy').append(this.createNode(data)).end();
1504
- this.$chart.prepend($newRootWrapper)
1505
- .find('.hierarchy:first').append($currentRoot.closest('ul').addClass('nodes'));
1506
- },
1507
- // exposed method
1508
- addParent: function ($currentRoot, data) {
1509
- this.buildParentNode($currentRoot, data);
1510
- if (!$currentRoot.children('.topEdge').length) {
1511
- $currentRoot.children('.title').after(`<i class="edge verticalEdge topEdge ${this.options.icons.theme}"></i>`);
1512
- }
1513
- if (this.isInAction($currentRoot)) {
1514
- this.switchVerticalArrow($currentRoot.children('.topEdge'));
1515
- }
1516
- },
1517
- // build the sibling nodes of specific node
1518
- buildSiblingNode: function ($nodeChart, data) {
1519
- var newSiblingCount = $.isArray(data) ? data.length : data.children.length;
1520
- var existingSibligCount = $nodeChart.parent().is('.nodes') ? $nodeChart.siblings().length + 1 : 1;
1521
- var siblingCount = existingSibligCount + newSiblingCount;
1522
- var insertPostion = (siblingCount > 1) ? Math.floor(siblingCount/2 - 1) : 0;
1523
- // just build the sibling nodes for the specific node
1524
- if ($nodeChart.closest('.nodes').parent().is('.hierarchy')) {
1525
- this.buildChildNode($nodeChart.parent().closest('.hierarchy'), data);
1526
- var $siblings = $nodeChart.parent().closest('.hierarchy').children('.nodes:last').children('.hierarchy');
1527
- if (existingSibligCount > 1) {
1528
- $siblings.eq(0).before($nodeChart.siblings().addBack().unwrap());
1529
- } else {
1530
- $siblings.eq(insertPostion).after($nodeChart.unwrap());
1531
- }
1532
- } else { // build the sibling nodes and parent node for the specific ndoe
1533
- this.buildHierarchy($nodeChart.parent().prepend($('<li class="hierarchy">')).children('.hierarchy:first'), data);
1534
- $nodeChart.prevAll('.hierarchy').children('.nodes').children().eq(insertPostion).after($nodeChart);
1535
- }
1536
- },
1537
- //
1538
- addSiblings: function ($node, data) {
1539
- this.buildSiblingNode($node.closest('.hierarchy'), data);
1540
- $node.closest('.nodes').data('siblingsLoaded', true);
1541
- if (!$node.children('.leftEdge').length) {
1542
- $node.children('.topEdge').after(`<i class="edge horizontalEdge rightEdge ${this.options.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${this.options.icons.theme}"></i>`);
1543
- }
1544
- if (this.isInAction($node)) {
1545
- this.switchHorizontalArrow($node);
1546
- $node.children('.topEdge').removeClass(this.options.icons.expandToUp).addClass(this.options.icons.collapseToDown);
1547
- }
1548
- },
1549
- // remove node and its descendent nodes
1550
- removeNodes: function ($node) {
1551
- var $wrapper = $node.closest('.hierarchy').parent();
1552
- if ($wrapper.parent().is('.hierarchy')) {
1553
- if (this.getNodeState($node, 'siblings').exist) {
1554
- $node.closest('.hierarchy').remove();
1555
- if ($wrapper.children().length === 1) {
1556
- $wrapper.find('.node:first .horizontalEdge').remove();
1557
- }
1558
- } else {
1559
- $wrapper.siblings('.node').find('.bottomEdge').remove()
1560
- .end().end().remove();
1561
- }
1562
- } else { // if $node is root node
1563
- $wrapper.closest('.orgchart').remove();
1564
- }
1565
- },
1566
- //
1567
- hideDropZones: function () {
1568
- // Remove all the 'this is a drop zone' indicators
1569
- var orgChartObj = this;
1570
- orgChartObj.$chart.find('.allowedDrop')
1571
- .removeClass('allowedDrop');
1572
- },
1573
- //
1574
- showDropZones: function (dragged) {
1575
- // Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later
1576
- // TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should!
1577
- var orgChartObj = this;
1578
- orgChartObj.$chart.find('.node')
1579
- .each(function (index, node) {
1580
- $(node).addClass('allowedDrop');
1581
- });
1582
- orgChartObj.$chart.data('dragged', $(dragged));
1583
- },
1584
- //
1585
- processExternalDrop: function (dropZone, dragged) {
1586
- // Allow an external drop event to be handled by one of our nodes
1587
- if (dragged) {
1588
- this.$chart.data('dragged', $(dragged));
1589
- }
1590
- var droppedOnNode = dropZone.closest('.node');
1591
- // would like to just call 'dropZoneHandler', but I can't reach it from here
1592
- // instead raise a drop event on the node element
1593
- droppedOnNode.triggerHandler({ 'type': 'drop' });
1594
- },
1595
- //
1596
- exportPDF: function(canvas, exportFilename){
1597
- var doc = {};
1598
- var docWidth = Math.floor(canvas.width);
1599
- var docHeight = Math.floor(canvas.height);
1600
- if (!window.jsPDF) {
1601
- window.jsPDF = window.jspdf.jsPDF;
1602
- }
1603
-
1604
- if (docWidth > docHeight) {
1605
- doc = new jsPDF({
1606
- orientation: 'landscape',
1607
- unit: 'px',
1608
- format: [docWidth, docHeight]
1609
- });
1610
- } else {
1611
- doc = new jsPDF({
1612
- orientation: 'portrait',
1613
- unit: 'px',
1614
- format: [docHeight, docWidth]
1615
- });
1616
- }
1617
- doc.addImage(canvas.toDataURL(), 'png', 0, 0);
1618
- doc.save(exportFilename + '.pdf');
1619
- },
1620
- //
1621
- exportPNG: function(canvas, exportFilename){
1622
- var that = this;
1623
- var isWebkit = 'WebkitAppearance' in document.documentElement.style;
1624
- var isFf = !!window.sidebar;
1625
- var isEdge = navigator.appName === 'Microsoft Internet Explorer' || (navigator.appName === "Netscape" && navigator.appVersion.indexOf('Edge') > -1);
1626
- var $chartContainer = this.$chartContainer;
1627
-
1628
- if ((!isWebkit && !isFf) || isEdge) {
1629
- window.navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png');
1630
- } else {
1631
- var selector = '.download-btn' + (that.options.chartClass !== '' ? '.' + that.options.chartClass : '');
1632
-
1633
- if (!$chartContainer.find(selector).length) {
1634
- $chartContainer.append('<a class="download-btn' + (that.options.chartClass !== '' ? ' ' + that.options.chartClass : '') + '"'
1635
- + ' download="' + exportFilename + '.png"></a>');
1636
- }
1637
-
1638
- $chartContainer.find(selector).attr('href', canvas.toDataURL())[0].click();
1639
- }
1640
- },
1641
- //
1642
- export: function (exportFilename, exportFileextension) {
1643
- var that = this;
1644
- exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;
1645
- exportFileextension = (typeof exportFileextension !== 'undefined') ? exportFileextension : this.options.exportFileextension;
1646
- if ($(this).children('.spinner').length) {
1647
- return false;
1648
- }
1649
- var $chartContainer = this.$chartContainer;
1650
- var $mask = $chartContainer.find('.mask');
1651
- if (!$mask.length) {
1652
- $chartContainer.append(`<div class="mask"><i class="${this.options.icons.theme} ${this.options.icons.spinner} spinner"></i></div>`);
1653
- } else {
1654
- $mask.removeClass('hidden');
1655
- }
1656
- var sourceChart = $chartContainer.addClass('canvasContainer').find('.orgchart:not(".hidden")').get(0);
1657
- var flag = that.options.direction === 'l2r' || that.options.direction === 'r2l';
1658
- html2canvas(sourceChart, {
1659
- 'width': flag ? sourceChart.clientHeight : sourceChart.clientWidth,
1660
- 'height': flag ? sourceChart.clientWidth : sourceChart.clientHeight,
1661
- 'onclone': function (cloneDoc) {
1662
- $(cloneDoc).find('.canvasContainer').css('overflow', 'visible')
1663
- .find('.orgchart:not(".hidden"):first').css('transform', '');
1664
- }
1665
- })
1666
- .then(function (canvas) {
1667
- $chartContainer.find('.mask').addClass('hidden');
1668
-
1669
- if (exportFileextension.toLowerCase() === 'pdf') {
1670
- that.exportPDF(canvas, exportFilename);
1671
- } else {
1672
- that.exportPNG(canvas, exportFilename);
1673
- }
1674
-
1675
- $chartContainer.removeClass('canvasContainer');
1676
- }, function () {
1677
- $chartContainer.removeClass('canvasContainer');
1678
- });
1679
- }
1680
- };
1681
-
1682
- $.fn.orgchart = function (opts) {
1683
- return new OrgChart(this, opts).init();
1684
- };
1685
-
1686
- }));