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