Skip to content

Commit 76797d5

Browse files
authored
Merge pull request #398 from richardwalker-iamtech/master
Updates for drag-and-drop. I'm very appreciated for you contributing such awesome PR that makes orgchart plugin be touch-enabled. 😊 @richardwalker-iamtech
2 parents 4713b96 + 56f5d5d commit 76797d5

File tree

1 file changed

+162
-61
lines changed

1 file changed

+162
-61
lines changed

src/js/jquery.orgchart.js

Lines changed: 162 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@
906906
var ghostNode, nodeCover;
907907
if (!document.querySelector('.ghost-node')) {
908908
ghostNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
909+
if (!ghostNode.classList) return;
909910
ghostNode.classList.add('ghost-node');
910911
nodeCover = document.createElementNS('http://www.w3.org/2000/svg','rect');
911912
ghostNode.appendChild(nodeCover);
@@ -945,17 +946,21 @@
945946
ghostNodeWrapper.src = 'data:image/svg+xml;utf8,' + (new XMLSerializer()).serializeToString(ghostNode);
946947
origEvent.dataTransfer.setDragImage(ghostNodeWrapper, xOffset, yOffset);
947948
} else {
948-
origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset);
949+
// IE/Edge do not support this, so only use it if we can
950+
if (origEvent.dataTransfer.setDragImage)
951+
origEvent.dataTransfer.setDragImage(ghostNode, xOffset, yOffset);
949952
}
950953
},
951954
//
952955
filterAllowedDropNodes: function ($dragged) {
953956
var opts = this.options;
954-
var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first');
955-
var $dragHier = $dragged.closest('table').find('.node');
957+
// what is being dragged? a node, or something within a node?
958+
var draggingNode = $dragged.closest('[draggable]').hasClass('node');
959+
var $dragZone = $dragged.closest('.nodes').siblings().eq(0).find('.node:first'); // parent node
960+
var $dragHier = $dragged.closest('table').find('.node'); // this node, and its children
956961
this.$chart.data('dragged', $dragged)
957962
.find('.node').each(function (index, node) {
958-
if ($dragHier.index(node) === -1) {
963+
if (!draggingNode || $dragHier.index(node) === -1) {
959964
if (opts.dropCriteria) {
960965
if (opts.dropCriteria($dragged, $dragZone, $(node))) {
961966
$(node).addClass('allowedDrop');
@@ -993,6 +998,19 @@
993998
dropHandler: function (event) {
994999
var $dropZone = $(event.delegateTarget);
9951000
var $dragged = this.$chart.data('dragged');
1001+
1002+
// Pass on drops which are not nodes (since they are not our doing)
1003+
if (!$dragged.hasClass('node')) {
1004+
this.$chart.triggerHandler({ 'type': 'otherdropped.orgchart', 'draggedItem': $dragged, 'dropZone': $dropZone });
1005+
return;
1006+
}
1007+
1008+
if (!$dropZone.hasClass('allowedDrop')) {
1009+
// We are trying to drop a node into a node which isn't allowed
1010+
// IE/Edge have a habit of allowing this, so we need our own double-check
1011+
return;
1012+
}
1013+
9961014
var $dragZone = $dragged.closest('.nodes').siblings().eq(0).children();
9971015
var dropEvent = $.Event('nodedrop.orgchart');
9981016
this.$chart.trigger(dropEvent, { 'draggedNode': $dragged, 'dragZone': $dragZone.children(), 'dropZone': $dropZone });
@@ -1039,95 +1057,149 @@
10391057
},
10401058
//
10411059
touchstartHandler: function (event) {
1042-
console.log("orgChart: touchstart 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", target=" + event.target.innerText);
1043-
if (this.touchHandled)
1044-
return;
1045-
this.touchHandled = true;
1046-
this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch
1047-
event.preventDefault();
1060+
if (this.touchHandled)
1061+
return;
1062+
1063+
if (event.touches && event.touches.length > 1)
1064+
return;
1065+
1066+
this.touchHandled = true;
1067+
this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch
1068+
event.preventDefault();
10481069
},
10491070
//
10501071
touchmoveHandler: function (event) {
10511072
if (!this.touchHandled)
10521073
return;
1074+
1075+
if (event.touches && event.touches.length > 1)
1076+
return;
1077+
10531078
event.preventDefault();
1079+
10541080
if (!this.touchMoved) {
1055-
var nodeIsSelected = $(this).hasClass('focused');
1056-
console.log("orgChart: touchmove 1: " + event.touches.length + " touches, we have not moved, so simulate a drag start", event.touches);
1057-
// TODO: visualise the start of the drag (as would happen on desktop)
1058-
this.simulateMouseEvent(event, 'dragstart');
1081+
// we do not bother with createGhostNode (dragstart does) since the touch event does not have a dataTransfer property
1082+
this.filterAllowedDropNodes($(event.currentTarget)); // will also set 'this.$chart.data('dragged')' for us
1083+
// create an image which can be used to illustrate the drag (our own createGhostNode)
1084+
this.touchDragImage = this.createDragImage(event, this.$chart.data('dragged')[0]);
10591085
}
10601086
this.touchMoved = true;
1061-
var $touching = $(document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY));
1062-
var $touchingNode = $touching.closest('div.node');
10631087

1064-
if ($touchingNode.length > 0) {
1065-
var touchingNodeElement = $touchingNode[0];
1066-
// TODO: simulate the dragover visualisation
1067-
if ($touchingNode.is('.allowedDrop')) {
1068-
console.log("orgChart: touchmove 2: this node (" + touchingNodeElement.id + ") is allowed to be a drop target");
1069-
this.touchTargetNode = touchingNodeElement;
1070-
} else {
1071-
console.log("orgChart: touchmove 3: this node (" + touchingNodeElement.id + ") is NOT allowed to be a drop target");
1072-
this.touchTargetNode = null;
1088+
// move our dragimage so it follows our finger
1089+
this.moveDragImage(event, this.touchDragImage);
1090+
1091+
var $touching = $(document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY));
1092+
var $touchingNodes = $touching.closest('div.node');
1093+
if ($touchingNodes.length > 0) {
1094+
var touchingNodeElement = $touchingNodes[0];
1095+
if ($touchingNodes.is('.allowedDrop')) {
1096+
this.touchTargetNode = touchingNodeElement;
10731097
}
1074-
} else {
1075-
console.log("orgchart: touchmove 4: not touching a node");
1098+
else {
1099+
this.touchTargetNode = null;
1100+
}
1101+
}
1102+
else {
10761103
this.touchTargetNode = null;
10771104
}
10781105
},
10791106
//
10801107
touchendHandler: function (event) {
1081-
console.log("orgChart: touchend 1: touchHandled=" + this.touchHandled + ", touchMoved=" + this.touchMoved + ", " + event.target.innerText + " ");
10821108
if (!this.touchHandled) {
1083-
console.log("orgChart: touchend 2: not handled by us, so aborting");
10841109
return;
10851110
}
1111+
this.destroyDragImage();
10861112
if (this.touchMoved) {
10871113
// we've had movement, so this was a 'drag' touch
10881114
if (this.touchTargetNode) {
1089-
console.log("orgChart: touchend 3: moved to a node, so simulating drop");
10901115
var fakeEventForDropHandler = { delegateTarget: this.touchTargetNode };
10911116
this.dropHandler(fakeEventForDropHandler);
10921117
this.touchTargetNode = null;
10931118
}
1094-
console.log("orgChart: touchend 4: simulating dragend");
1095-
this.simulateMouseEvent(event, 'dragend');
1119+
this.dragendHandler(event);
10961120
}
10971121
else {
1098-
// we did not move, so assume this was a 'press' touch
1099-
console.log("orgChart: touchend 5: moved, so simulating click");
1100-
this.simulateMouseEvent(event, 'click');
1122+
// we did not move, so this was a 'press' touch (fake a click)
1123+
var firstTouch = event.changedTouches[0];
1124+
var fakeMouseClickEvent = document.createEvent('MouseEvents');
1125+
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);
1126+
event.target.dispatchEvent(fakeMouseClickEvent);
11011127
}
11021128
this.touchHandled = false;
11031129
},
1104-
// simulate a mouse event (so we can fake them on a touch device)
1105-
simulateMouseEvent: function (event, simulatedType) {
1106-
// Ignore multi-touch events
1107-
if (event.originalEvent.touches.length > 1) {
1108-
return;
1130+
//
1131+
createDragImage: function (event, source) {
1132+
var dragImage = source.cloneNode(true);
1133+
this.copyStyle(source, dragImage);
1134+
dragImage.style.top = dragImage.style.left = '-9999px';
1135+
var sourceRectangle = source.getBoundingClientRect();
1136+
var sourcePoint = this.getTouchPoint(event);
1137+
this.touchDragImageOffset = { x: sourcePoint.x - sourceRectangle.left, y: sourcePoint.y - sourceRectangle.top };
1138+
dragImage.style.opacity = '0.5';
1139+
document.body.appendChild(dragImage);
1140+
return dragImage;
1141+
},
1142+
//
1143+
destroyDragImage: function () {
1144+
if (this.touchDragImage && this.touchDragImage.parentElement)
1145+
this.touchDragImage.parentElement.removeChild(this.touchDragImage);
1146+
this.touchDragImageOffset = null;
1147+
this.touchDragImage = null;
1148+
},
1149+
//
1150+
copyStyle: function (src, dst) {
1151+
// remove potentially troublesome attributes
1152+
var badAttributes = ['id', 'class', 'style', 'draggable'];
1153+
badAttributes.forEach(function (att) {
1154+
dst.removeAttribute(att);
1155+
});
1156+
// copy canvas content
1157+
if (src instanceof HTMLCanvasElement) {
1158+
var cSrc = src, cDst = dst;
1159+
cDst.width = cSrc.width;
1160+
cDst.height = cSrc.height;
1161+
cDst.getContext('2d').drawImage(cSrc, 0, 0);
1162+
}
1163+
// copy style (without transitions)
1164+
var cs = getComputedStyle(src);
1165+
for (var i = 0; i < cs.length; i++) {
1166+
var key = cs[i];
1167+
if (key.indexOf('transition') < 0) {
1168+
dst.style[key] = cs[key];
1169+
}
1170+
}
1171+
dst.style.pointerEvents = 'none';
1172+
// and repeat for all children
1173+
for (var i = 0; i < src.children.length; i++) {
1174+
this.copyStyle(src.children[i], dst.children[i]);
1175+
}
1176+
},
1177+
//
1178+
getTouchPoint: function (event) {
1179+
if (event && event.touches) {
1180+
event = event.touches[0];
11091181
}
1110-
var touch = event.originalEvent.changedTouches[0];
1111-
var simulatedEvent = document.createEvent('MouseEvents');
1112-
simulatedEvent.initMouseEvent(
1113-
simulatedType, // type
1114-
true, // bubbles
1115-
true, // cancelable
1116-
window, // view
1117-
1, // detail
1118-
touch.screenX, // screenX
1119-
touch.screenY, // screenY
1120-
touch.clientX, // clientX
1121-
touch.clientY, // clientY
1122-
false, // ctrlKey
1123-
false, // altKey
1124-
false, // shiftKey
1125-
false, // metaKey
1126-
0, // button
1127-
null // relatedTarget
1128-
);
1129-
// Dispatch the simulated event to the target element
1130-
event.target.dispatchEvent(simulatedEvent);
1182+
return {
1183+
x: event.clientX,
1184+
y: event.clientY
1185+
};
1186+
},
1187+
//
1188+
moveDragImage: function (event, image) {
1189+
if (!event || !image)
1190+
return;
1191+
var orgChartMaster = this;
1192+
requestAnimationFrame(function () {
1193+
var pt = orgChartMaster.getTouchPoint(event);
1194+
var s = image.style;
1195+
s.position = 'absolute';
1196+
s.pointerEvents = 'none';
1197+
s.zIndex = '999999';
1198+
if (orgChartMaster.touchDragImageOffset) {
1199+
s.left = Math.round(pt.x - orgChartMaster.touchDragImageOffset.x) + 'px';
1200+
s.top = Math.round(pt.y - orgChartMaster.touchDragImageOffset.y) + 'px';
1201+
}
1202+
});
11311203
},
11321204
//
11331205
bindDragDrop: function ($node) {
@@ -1367,6 +1439,35 @@
13671439
}
13681440
},
13691441
//
1442+
hideDropZones: function () {
1443+
// Remove all the 'this is a drop zone' indicators
1444+
var orgChartObj = this;
1445+
orgChartObj.$chart.find('.allowedDrop')
1446+
.removeClass('allowedDrop');
1447+
},
1448+
//
1449+
showDropZones: function (dragged) {
1450+
// Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later
1451+
// TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should!
1452+
var orgChartObj = this;
1453+
orgChartObj.$chart.find('.node')
1454+
.each(function (index, node) {
1455+
$(node).addClass('allowedDrop');
1456+
});
1457+
orgChartObj.$chart.data('dragged', $(dragged));
1458+
},
1459+
//
1460+
processExternalDrop: function (dropZone, dragged) {
1461+
// Allow an external drop event to be handled by one of our nodes
1462+
if (dragged) {
1463+
this.$chart.data('dragged', $(dragged));
1464+
}
1465+
var droppedOnNode = dropZone.closest('.node');
1466+
// would like to just call 'dropZoneHandler', but I can't reach it from here
1467+
// instead raise a drop event on the node element
1468+
droppedOnNode.triggerHandler({ 'type': 'drop' });
1469+
},
1470+
//
13701471
exportPDF: function(canvas, exportFilename){
13711472
var doc = {};
13721473
var docWidth = Math.floor(canvas.width);

0 commit comments

Comments
 (0)