|
898 | 898 | var ghostNode, nodeCover;
|
899 | 899 | if (!document.querySelector('.ghost-node')) {
|
900 | 900 | ghostNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
| 901 | + if (!ghostNode.classList) return; |
901 | 902 | ghostNode.classList.add('ghost-node');
|
902 | 903 | nodeCover = document.createElementNS('http://www.w3.org/2000/svg','rect');
|
903 | 904 | ghostNode.appendChild(nodeCover);
|
|
936 | 937 | ghostNodeWrapper.src = 'data:image/svg+xml;utf8,' + (new XMLSerializer()).serializeToString(ghostNode);
|
937 | 938 | origEvent.dataTransfer.setDragImage(ghostNodeWrapper, xOffset, yOffset);
|
938 | 939 | } else {
|
939 |
| - origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset); |
| 940 | + // IE/Edge do not support this, so only use it if we can |
| 941 | + if (origEvent.dataTransfer.setDragImage) |
| 942 | + origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset); |
940 | 943 | }
|
941 | 944 | },
|
942 | 945 | //
|
943 | 946 | filterAllowedDropNodes: function ($dragged) {
|
944 | 947 | var opts = this.options;
|
945 |
| - var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); |
946 |
| - var $dragHier = $dragged.closest('table').find('.node'); |
| 948 | + // what is being dragged? a node, or something within a node? |
| 949 | + var draggingNode = $dragged.closest('[draggable]').hasClass('node'); |
| 950 | + var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); // parent node |
| 951 | + var $dragHier = $dragged.closest('table').find('.node'); // this node, and its children |
947 | 952 | this.$chart.data('dragged', $dragged)
|
948 | 953 | .find('.node').each(function (index, node) {
|
949 |
| - if ($dragHier.index(node) === -1) { |
| 954 | + if (!draggingNode || $dragHier.index(node) === -1) { |
950 | 955 | if (opts.dropCriteria) {
|
951 | 956 | if (opts.dropCriteria($dragged, $dragZone, $(node))) {
|
952 | 957 | $(node).addClass('allowedDrop');
|
|
980 | 985 | dropHandler: function (event) {
|
981 | 986 | var $dropZone = $(event.delegateTarget);
|
982 | 987 | var $dragged = this.$chart.data('dragged');
|
| 988 | + |
| 989 | + // Pass on drops which are not nodes (since they are not our doing) |
| 990 | + if (!$dragged.hasClass('node')) { |
| 991 | + this.$chart.triggerHandler({ 'type': 'otherdropped.orgchart', 'draggedItem': $dragged, 'dropZone': $dropZone }); |
| 992 | + return; |
| 993 | + } |
| 994 | + |
| 995 | + if (!$dropZone.hasClass('allowedDrop')) { |
| 996 | + // We are trying to drop a node into a node which isn't allowed |
| 997 | + // IE/Edge have a habit of allowing this, so we need our own double-check |
| 998 | + return; |
| 999 | + } |
| 1000 | + |
983 | 1001 | var $dragZone = $dragged.closest('.nodes').siblings().eq(0).children();
|
984 | 1002 | var dropEvent = $.Event('nodedrop.orgchart');
|
985 | 1003 | this.$chart.trigger(dropEvent, { 'draggedNode': $dragged, 'dragZone': $dragZone.children(), 'dropZone': $dropZone });
|
|
1026 | 1044 | },
|
1027 | 1045 | //
|
1028 | 1046 | touchstartHandler: function (event) {
|
1029 |
| - console.log("orgChart: touchstart 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", target=" + event.target.innerText); |
1030 |
| - if (this.touchHandled) |
1031 |
| - return; |
1032 |
| - this.touchHandled = true; |
1033 |
| - this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch |
1034 |
| - event.preventDefault(); |
| 1047 | + if (this.touchHandled) |
| 1048 | + return; |
| 1049 | + |
| 1050 | + if (event.touches && event.touches.length > 1) |
| 1051 | + return; |
| 1052 | + |
| 1053 | + this.touchHandled = true; |
| 1054 | + this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch |
| 1055 | + event.preventDefault(); |
1035 | 1056 | },
|
1036 | 1057 | //
|
1037 | 1058 | touchmoveHandler: function (event) {
|
1038 | 1059 | if (!this.touchHandled)
|
1039 | 1060 | return;
|
| 1061 | + |
| 1062 | + if (event.touches && event.touches.length > 1) |
| 1063 | + return; |
| 1064 | + |
1040 | 1065 | event.preventDefault();
|
| 1066 | + |
1041 | 1067 | if (!this.touchMoved) {
|
1042 |
| - var nodeIsSelected = $(this).hasClass('focused'); |
1043 |
| - console.log("orgChart: touchmove 1: " + event.touches.length + " touches, we have not moved, so simulate a drag start", event.touches); |
1044 |
| - // TODO: visualise the start of the drag (as would happen on desktop) |
1045 |
| - this.simulateMouseEvent(event, 'dragstart'); |
| 1068 | + // we do not bother with createGhostNode (dragstart does) since the touch event does not have a dataTransfer property |
| 1069 | + this.filterAllowedDropNodes($(event.currentTarget)); // will also set 'this.$chart.data('dragged')' for us |
| 1070 | + // create an image which can be used to illustrate the drag (our own createGhostNode) |
| 1071 | + this.touchDragImage = this.createDragImage(event, this.$chart.data('dragged')[0]); |
1046 | 1072 | }
|
1047 | 1073 | this.touchMoved = true;
|
| 1074 | + |
| 1075 | + // move our dragimage so it follows our finger |
| 1076 | + this.moveDragImage(event, this.touchDragImage); |
| 1077 | + |
1048 | 1078 | var $touching = $(document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY));
|
1049 |
| - var $touchingNode = $touching.closest('div.node'); |
1050 |
| - |
1051 |
| - if ($touchingNode.length > 0) { |
1052 |
| - var touchingNodeElement = $touchingNode[0]; |
1053 |
| - // TODO: simulate the dragover visualisation |
1054 |
| - if ($touchingNode.is('.allowedDrop')) { |
1055 |
| - console.log("orgChart: touchmove 2: this node (" + touchingNodeElement.id + ") is allowed to be a drop target"); |
1056 |
| - this.touchTargetNode = touchingNodeElement; |
1057 |
| - } else { |
1058 |
| - console.log("orgChart: touchmove 3: this node (" + touchingNodeElement.id + ") is NOT allowed to be a drop target"); |
1059 |
| - this.touchTargetNode = null; |
| 1079 | + var $touchingNodes = $touching.closest('div.node'); |
| 1080 | + if ($touchingNodes.length > 0) { |
| 1081 | + var touchingNodeElement = $touchingNodes[0]; |
| 1082 | + if ($touchingNodes.is('.allowedDrop')) { |
| 1083 | + this.touchTargetNode = touchingNodeElement; |
1060 | 1084 | }
|
1061 |
| - } else { |
1062 |
| - console.log("orgchart: touchmove 4: not touching a node"); |
| 1085 | + else { |
| 1086 | + this.touchTargetNode = null; |
| 1087 | + } |
| 1088 | + } |
| 1089 | + else { |
1063 | 1090 | this.touchTargetNode = null;
|
1064 | 1091 | }
|
1065 | 1092 | },
|
1066 | 1093 | //
|
1067 | 1094 | touchendHandler: function (event) {
|
1068 |
| - console.log("orgChart: touchend 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", " + event.target.innerText + " "); |
1069 | 1095 | if (!this.touchHandled) {
|
1070 |
| - console.log("orgChart: touchend 2: not handled by us, so aborting"); |
1071 | 1096 | return;
|
1072 | 1097 | }
|
| 1098 | + this.destroyDragImage(); |
1073 | 1099 | if (this.touchMoved) {
|
1074 | 1100 | // we've had movement, so this was a 'drag' touch
|
1075 | 1101 | if (this.touchTargetNode) {
|
1076 |
| - console.log("orgChart: touchend 3: moved to a node, so simulating drop"); |
1077 | 1102 | var fakeEventForDropHandler = { delegateTarget: this.touchTargetNode };
|
1078 | 1103 | this.dropHandler(fakeEventForDropHandler);
|
1079 | 1104 | this.touchTargetNode = null;
|
1080 | 1105 | }
|
1081 |
| - console.log("orgChart: touchend 4: simulating dragend"); |
1082 |
| - this.simulateMouseEvent(event, 'dragend'); |
| 1106 | + this.dragendHandler(event); |
1083 | 1107 | }
|
1084 | 1108 | else {
|
1085 |
| - // we did not move, so assume this was a 'press' touch |
1086 |
| - console.log("orgChart: touchend 5: moved, so simulating click"); |
1087 |
| - this.simulateMouseEvent(event, 'click'); |
| 1109 | + // we did not move, so this was a 'press' touch (fake a click) |
| 1110 | + var firstTouch = event.changedTouches[0]; |
| 1111 | + var fakeMouseClickEvent = document.createEvent('MouseEvents'); |
| 1112 | + 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); |
| 1113 | + event.target.dispatchEvent(fakeMouseClickEvent); |
1088 | 1114 | }
|
1089 | 1115 | this.touchHandled = false;
|
1090 | 1116 | },
|
1091 |
| - // simulate a mouse event (so we can fake them on a touch device) |
1092 |
| - simulateMouseEvent: function (event, simulatedType) { |
1093 |
| - // Ignore multi-touch events |
1094 |
| - if (event.originalEvent.touches.length > 1) { |
1095 |
| - return; |
| 1117 | + // |
| 1118 | + createDragImage: function (event, source) { |
| 1119 | + var dragImage = source.cloneNode(true); |
| 1120 | + this.copyStyle(source, dragImage); |
| 1121 | + dragImage.style.top = dragImage.style.left = '-9999px'; |
| 1122 | + var sourceRectangle = source.getBoundingClientRect(); |
| 1123 | + var sourcePoint = this.getTouchPoint(event); |
| 1124 | + this.touchDragImageOffset = { x: sourcePoint.x - sourceRectangle.left, y: sourcePoint.y - sourceRectangle.top }; |
| 1125 | + dragImage.style.opacity = '0.5'; |
| 1126 | + document.body.appendChild(dragImage); |
| 1127 | + return dragImage; |
| 1128 | + }, |
| 1129 | + // |
| 1130 | + destroyDragImage: function () { |
| 1131 | + if (this.touchDragImage && this.touchDragImage.parentElement) |
| 1132 | + this.touchDragImage.parentElement.removeChild(this.touchDragImage); |
| 1133 | + this.touchDragImageOffset = null; |
| 1134 | + this.touchDragImage = null; |
| 1135 | + }, |
| 1136 | + // |
| 1137 | + copyStyle: function (src, dst) { |
| 1138 | + // remove potentially troublesome attributes |
| 1139 | + var badAttributes = ['id', 'class', 'style', 'draggable']; |
| 1140 | + badAttributes.forEach(function (att) { |
| 1141 | + dst.removeAttribute(att); |
| 1142 | + }); |
| 1143 | + // copy canvas content |
| 1144 | + if (src instanceof HTMLCanvasElement) { |
| 1145 | + var cSrc = src, cDst = dst; |
| 1146 | + cDst.width = cSrc.width; |
| 1147 | + cDst.height = cSrc.height; |
| 1148 | + cDst.getContext('2d').drawImage(cSrc, 0, 0); |
| 1149 | + } |
| 1150 | + // copy style (without transitions) |
| 1151 | + var cs = getComputedStyle(src); |
| 1152 | + for (var i = 0; i < cs.length; i++) { |
| 1153 | + var key = cs[i]; |
| 1154 | + if (key.indexOf('transition') < 0) { |
| 1155 | + dst.style[key] = cs[key]; |
| 1156 | + } |
1096 | 1157 | }
|
1097 |
| - var touch = event.originalEvent.changedTouches[0]; |
1098 |
| - var simulatedEvent = document.createEvent('MouseEvents'); |
1099 |
| - simulatedEvent.initMouseEvent( |
1100 |
| - simulatedType, // type |
1101 |
| - true, // bubbles |
1102 |
| - true, // cancelable |
1103 |
| - window, // view |
1104 |
| - 1, // detail |
1105 |
| - touch.screenX, // screenX |
1106 |
| - touch.screenY, // screenY |
1107 |
| - touch.clientX, // clientX |
1108 |
| - touch.clientY, // clientY |
1109 |
| - false, // ctrlKey |
1110 |
| - false, // altKey |
1111 |
| - false, // shiftKey |
1112 |
| - false, // metaKey |
1113 |
| - 0, // button |
1114 |
| - null // relatedTarget |
1115 |
| - ); |
1116 |
| - // Dispatch the simulated event to the target element |
1117 |
| - event.target.dispatchEvent(simulatedEvent); |
| 1158 | + dst.style.pointerEvents = 'none'; |
| 1159 | + // and repeat for all children |
| 1160 | + for (var i = 0; i < src.children.length; i++) { |
| 1161 | + this.copyStyle(src.children[i], dst.children[i]); |
| 1162 | + } |
| 1163 | + }, |
| 1164 | + // |
| 1165 | + getTouchPoint: function (event) { |
| 1166 | + if (event && event.touches) { |
| 1167 | + event = event.touches[0]; |
| 1168 | + } |
| 1169 | + return { |
| 1170 | + x: event.clientX, |
| 1171 | + y: event.clientY |
| 1172 | + }; |
| 1173 | + }, |
| 1174 | + // |
| 1175 | + moveDragImage: function (event, image) { |
| 1176 | + if (!event || !image) |
| 1177 | + return; |
| 1178 | + var orgChartMaster = this; |
| 1179 | + requestAnimationFrame(function () { |
| 1180 | + var pt = orgChartMaster.getTouchPoint(event); |
| 1181 | + var s = image.style; |
| 1182 | + s.position = 'absolute'; |
| 1183 | + s.pointerEvents = 'none'; |
| 1184 | + s.zIndex = '999999'; |
| 1185 | + if (orgChartMaster.touchDragImageOffset) { |
| 1186 | + s.left = Math.round(pt.x - orgChartMaster.touchDragImageOffset.x) + 'px'; |
| 1187 | + s.top = Math.round(pt.y - orgChartMaster.touchDragImageOffset.y) + 'px'; |
| 1188 | + } |
| 1189 | + }); |
1118 | 1190 | },
|
1119 | 1191 | //
|
1120 | 1192 | bindDragDrop: function ($node) {
|
|
1350 | 1422 | }
|
1351 | 1423 | },
|
1352 | 1424 | //
|
| 1425 | + hideDropZones: function () { |
| 1426 | + // Remove all the 'this is a drop zone' indicators |
| 1427 | + var orgChartObj = this; |
| 1428 | + orgChartObj.$chart.find('.allowedDrop') |
| 1429 | + .removeClass('allowedDrop'); |
| 1430 | + }, |
| 1431 | + // |
| 1432 | + showDropZones: function (dragged) { |
| 1433 | + // Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later |
| 1434 | + // TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should! |
| 1435 | + var orgChartObj = this; |
| 1436 | + orgChartObj.$chart.find('.node') |
| 1437 | + .each(function (index, node) { |
| 1438 | + $(node).addClass('allowedDrop'); |
| 1439 | + }); |
| 1440 | + orgChartObj.$chart.data('dragged', $(dragged)); |
| 1441 | + }, |
| 1442 | + // |
| 1443 | + processExternalDrop: function (dropZone, dragged) { |
| 1444 | + // Allow an external drop event to be handled by one of our nodes |
| 1445 | + if (dragged) { |
| 1446 | + this.$chart.data('dragged', $(dragged)); |
| 1447 | + } |
| 1448 | + var droppedOnNode = dropZone.closest('.node'); |
| 1449 | + // would like to just call 'dropZoneHandler', but I can't reach it from here |
| 1450 | + // instead raise a drop event on the node element |
| 1451 | + droppedOnNode.triggerHandler({ 'type': 'drop' }); |
| 1452 | + }, |
| 1453 | + // |
1353 | 1454 | export: function (exportFilename, exportFileextension) {
|
1354 | 1455 | var that = this;
|
1355 | 1456 | exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;
|
|
0 commit comments