Dragon Drop
I've done a ridiculous amount of fiddling. So, so much jsFiddling.
This is not a robust, or complete solution; I may never quite come up with one. If anyone has any better solutions, I'm all ears -- I didn't want to have to do it this way, but it's the only way I've been able to uncover so far. The following jsFiddle, and the information I am about to vomit up, worked for me in this particular instance with my particular versions of Firefox and Chrome on my particular WAMP setup and computer. Don't come crying to me when it doesn't work on your website. This drag-and-drop crap is clearly every man for himself.
jsFiddle: Chase Moskal's Dragon Drop
So, I was boring my girlfriend's brains out, and she thought I kept saying "dragon drop" when really, I was just saying "drag-and-drop". It stuck, so that's what I call my little JavaScript buddy I've created for handling these drag-and-drop situations.
Turns out -- it's a bit of a nightmare. The HTML5 Drag-and-Drop API even at first glance, is horrible. Then, you almost warm up to it, as you start to understand and accept the way it's supposed to work.. Then you realize what a terrifying nightmare it actually is, as you learn how Firefox and Chrome go about this specification in their own special way, and seem to completely ignore all of your needs. You find yourself asking questions like: "Wait, what element is even being dragged right now? How to do I get that information? How do I cancel this drag operation? How can I stop this particular browser's unique default handling of this situation?"... The answers to your questions: "You're on your own, LOSER! Keep hacking things in, until something works!".
So, here's how I accomplished Precise Drag and Drop of Arbitrary HTML Elements within, around, and between multiple contenteditable's. (note: I'm not going fully in-depth with every detail, you'll have to look at the jsFiddle for that -- I'm just rambling off seemingly relevant details that I remember from the experience, as I have limited time)
My Solution
- First, I applied CSS to the draggables (fancybox) -- we needed
user-select:none; user-drag:element;
on the fancy box, and then specifically user-drag:none;
on the image within the fancy box (and any other elements, why not?). Unfortunately, this was not quite enough for Firefox, which required attribute draggable="false"
to be explicitly set on the image to prevent it from being draggable.
- Next, I applied attributes
draggable="true"
and dropzone="copy"
onto the contenteditables.
To the draggables (fancyboxes), I bind a handler for dragstart
. We set the dataTransfer to copy a blank string of HTML ' ' -- because we need to trick it into thinking we are going to drag HTML, but we are cancelling out any default behavior. Sometimes default behavior slips in somehow, and it results in a duplicate (as we do the insertion ourselves), so now the worst glitch is a ' ' (space) being inserted when a drag fails. We couldn't rely on the default behavior, as it would fail to often, so I found this to be the most versatile solution.
DD.$draggables.off('dragstart').on('dragstart',function(event){
var e=event.originalEvent;
$(e.target).removeAttr('dragged');
var dt=e.dataTransfer,
content=e.target.outerHTML;
var is_draggable = DD.$draggables.is(e.target);
if (is_draggable) {
dt.effectAllowed = 'copy';
dt.setData('text/plain',' ');
DD.dropLoad=content;
$(e.target).attr('dragged','dragged');
}
});
To the dropzones, I bind a handler for dragleave
and drop
. The dragleave handler exists only for Firefox, as in Firefox, the drag-drop would work (Chrome denies you by default) when you tried to drag it outside the contenteditable, so it performs a quick check against the Firefox-only relatedTarget
. Huff.
Chrome and Firefox have different ways of acquiring the Range object, so effort had to be put in to do it differently for each browser in the drop event. Chrome builds a range based on mouse-coordinates (yup that's right), but Firefox provides it in the event data. document.execCommand('insertHTML',false,blah)
turns out to be how we handle the drop. OH, I forgot to mention -- we can't use dataTransfer.getData()
on Chrome to get our dragstart set HTML -- it appears to be some kind of weird bug in the specification. Firefox calls the spec out on it's bullcrap and gives us the data anyways -- but Chrome doesn't, so we bend over backwards and to set the content to a global, and go through hell to kill all the default behavior...
DD.$dropzones.off('dragleave').on('dragleave',function(event){
var e=event.originalEvent;
var dt=e.dataTransfer;
var relatedTarget_is_dropzone = DD.$dropzones.is(e.relatedTarget);
var relatedTarget_within_dropzone = DD.$dropzones.has(e.relatedTarget).length>0;
var acceptable = relatedTarget_is_dropzone||relatedTarget_within_dropzone;
if (!acceptable) {
dt.dropEffect='none';
dt.effectAllowed='null';
}
});
DD.$dropzones.off('drop').on('drop',function(event){
var e=event.originalEvent;
if (!DD.dropLoad) return false;
var range=null;
if (document.caretRangeFromPoint) { // Chrome
range=document.caretRangeFromPoint(e.clientX,e.clientY);
}
else if (e.rangeParent) { // Firefox
range=document.createRange(); range.setStart(e.rangeParent,e.rangeOffset);
}
var sel = window.getSelection();
sel.removeAllRanges(); sel.addRange(range);
$(sel.anchorNode).closest(DD.$dropzones.selector).get(0).focus(); // essential
document.execCommand('insertHTML',false,'<param name="dragonDropMarker" />'+DD.dropLoad);
sel.removeAllRanges();
// verification with dragonDropMarker
var $DDM=$('param[name="dragonDropMarker"]');
var insertSuccess = $DDM.length>0;
if (insertSuccess) {
$(DD.$draggables.selector).filter('[dragged]').remove();
$DDM.remove();
}
DD.dropLoad=null;
DD.bindDraggables();
e.preventDefault();
});
Okay, I'm sick of this. I've wrote all I want to about this. I'm calling it a day, and might update this if I think of anything important.
Thanks everybody. //Chase.
Best Answer
I realise this question is over 4 years old, but maybe someone else might also need this information :)
So as shrishaster asked in the comments on Kim Stacks answer: "How can i do same thing for Texts instead of Images?"
The fix for this is a minor tweak on the dropcopy function given by Kim Stacks:
By doing this you make a clone of the original paragraf content :)
I hope this will help at least some.