13
votes

I'm making extensive use of the HTML5 native drag & drop, and it's almost entirely behaving itself, with one small exception.

I'm trying to highlight my dropzones when anything is dragged over the page. I originally tried to accomplish this by putting jQuery listeners on the document body, like this:

$("body").live('dragover',function(event){lightdz(event)});
$("body").live('dragexit dragleave drop',function(event){dimdz(event)});

with lightdz() and dimdz() changing the background-color style property of all dropzones on the page to make them stand out. This didn't work. Whenever a dragged object entered a child element on the page (like a div container), the listener would flag this up as a dragleave event and dim the dropzones.

I got around this by applying the listener to all visible elements on the page, instead of just the body. There was occasionally a slight visible flickering on the dropzones when it crossed the boundary between one element and another, but it looked fine.

Anyway, now I've changed lightdz() and dimdz() so that they apply a quick jQuery fadeTo() animation to all non-dropzones. This looks awesome when it works, and makes it very apparent to the user what they can and can't drop things on. The trouble is that when it passes between element boundaries, it applies the fade animation. This is a lot more apparent than the occasional flicker of background-color, especially since if the object is dragged over multiple boundaries very quickly, it will queue the animations and have the page fade in and out repeatedly.

Even if I don't bother with the fadeTo() animation, and just change the opacity, it's a lot more visible than the background-color flicker, because the entire page changes rather than just the dropzone elements.

Is there any way to reference the entire page as a single element for purposes of dragover and dragleave events? Failing that, is there any way to detect a drop that takes place outside of the browser window? If I skip the dragleave event, it looks fine, but if any object is dragged over the browser window and then dropped outside it, the whole page stays faded.

3

3 Answers

13
votes

I'm genuinely embarrassed by how easy this one was.

$("*:visible").live('dragenter dragover',function(event){lightdz(event)});

$("#page").live('dragleave dragexit',function(event)
{
    if(event.pageX == "0")
       dimdz(event);
});

$("*:visible").live('drop',function(event){dimdz(event)});

#page is a page-wide container. If the dragleave event takes the dragged object outside of the browser window, event.pageX will have a value of 0. If it happens anywhere else, it'll have a non-zero value.

2
votes

I may be getting overly complex here but I would do something like this:

var draggingFile = false;
var event2;

//elements with the class hotspots are OK
var hotspots = $(".hotspots");

//Handlers on the body for drag start & stop
$("body").live("dragover", function(event){ draggingFile = true; event2 = event; });
$("body").live("dragexit dragleave drop", function(event){ draggingFile = false; event2 = event; });

//Function checks to see if file is being dragged over an OK hotspot regardless of other elements infront
var isTargetOK = function(x, y){
    hotspots.each(function(i, el){
        el2 = $(el);
        var pos = el2.offset();
        if(x => pos.left && x <= pos.left+el2.width() && y => pos.top && y <= post.top+el2.height()){
            return true;
        }
    });
    return false;
};

//Mousemove handler on body
$("body").mousemove(function(e){
    //if user is dragging a file
    if(draggingFile){
        //Check to see if this is an OK element with mouse X & Y
        if(isOKTarget(e.pageX, e.pageY)){
            //Light em' up!
            lightdz(event2);
        } else { /* Fade em' :( */ dimdz(event2); }
    } else {
        dimdz(); //Having no parematers means just makes sure hotspots are off
    }
});

BTW that's probably not going to work straight off the bat, so you'll have to tweak it a bit to work with your code.

1
votes

I tried the accepted solution here, but ended up using setTimeout to overcome the issue. I was having a ton of trouble with the page-wide container blocking the drop element if it was floated on top, and still causing the problem if it was the drop element.

<body style="border: 1px solid black;">
    <div id="d0" style="border: 1px solid black;">&nbsp;</div>
    <div id="d1" style="border: 1px solid black; display: none; background-color: red;">-&gt; drop here &lt;-</div>
    <div id="d2" style="border: 1px solid black;">&nbsp;</div>
    <div style="float: left;">other element</div>
    <div style="float: left;">&nbsp;-&nbsp;</div>
    <div style="float: left;">another element</div>
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
</body>
<script type="text/javascript">
    var resetTimer;

    var f = function(e)
    {
        if (e.type == "dragover")
        {
            e.stopPropagation();
            e.preventDefault();
            if (resetTimer)
            {
                clearTimeout(resetTimer);
            }
            document.getElementById('d1').style.display = '';
        }
        else
        {
            var f = function()
            {
                document.getElementById('d1').style.display = 'none';
            };
            resetTimer = window.setTimeout(f, 25);  
        }
    };

    document.body.addEventListener("dragover", f, true);
    document.body.addEventListener("dragleave", f, true);
    document.getElementById('d1').addEventListener("drop", function(e){ f(); alert('dropped'); }, false);
</script>

If you were to just call f(); instead of window.setTimeout(f, 250);, you'll see some nasty flickering of the element showing and hiding.

http://jsfiddle.net/guYWx/