17

I'm trying to reproduce the way GMail handles html5 drag/drop attachments -- where as soon as you drag files over the page, it displays a new element for you to drop them on. I got that part worked out (it wasn't as straight forward as I thought it would be).

Now I'm trying to polish it up by changing the mouse cursor when the mouse is over any other element other than the drop element, to tell the user dropping isn't allowed here. I imagine I can do it with a custom cursor, but that does not appear to be what GMail is doing. The spec would suggest it's possible to change the mouse cursor as well, but I can't seem to get it working right, using dropzone/effectAllowed.

Any help would be appreciated, here's my current setup: http://jsfiddle.net/guYWx/1/

ETA: Here's what I ended up with: http://jsfiddle.net/guYWx/16/

<body style="border: 1px solid black;">
    <div id="d0" style="border: 1px solid black;">drag files onto this page</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;">and stuff will happen</div>
    <div style="float: left;">mouse them all over&nbsp;</div>
    <div style="float: left;">these elements</div>
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
    <div>end page</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
    var resetTimer;

    var reset = function()
    {
        $('#d1').hide();
    };

    var f = function(e)
    {
        var srcElement = e.srcElement? e.srcElement : e.target;

        if ($.inArray('Files', e.dataTransfer.types) > -1)
        {
            e.stopPropagation();
            e.preventDefault();

            e.dataTransfer.dropEffect = (srcElement.id == 'd1') ? 'copy' : 'none';

            if (e.type == "dragover")
            {
                if (resetTimer)
                {
                    clearTimeout(resetTimer);
                }
                $('#d1').show();
                console.info('dropped on <' + srcElement.tagName.toLowerCase() + ' id="' + srcElement.id + '">\n\ne.dataTransfer.types is ' + e.dataTransfer.types + '\n\ne.dataTransfer.files.length is ' + e.dataTransfer.files.length);

            }
            else if (e.type == "dragleave")
            {
                resetTimer = window.setTimeout(reset, 25);
            }
            else if (e.type == "drop")
            {
                reset();
                alert('dropped on <' + srcElement.tagName.toLowerCase() + ' id="' + srcElement.id + '">\n\ne.dataTransfer.files.length is ' + (e.dataTransfer.files ? e.dataTransfer.files.length : 0));
            }
        }
    };

    document.body.addEventListener("dragleave", f, false);
    document.body.addEventListener("dragover", f, false);
    document.body.addEventListener("drop", f, false);
</script>
Langdon
  • 19,875
  • 18
  • 88
  • 107
  • Hi, I've been battling with this myself for hours. Your code works much more nicely then mine. Can you explain what purpose the timeout delay on the reset serves? – benb Feb 29 '12 at 16:11
  • 1
    It prevents false positives for the dragleave event. When you bind dragover/dragleave to an element with a bunch of sub-elements, the events will fire when you mouse from sub-element to sub-element. I replaced the timeout with a call to `reset`, so you can see how bad it is when you drag over: http://jsfiddle.net/guYWx/20/ (lots of hiding/showing in Chrome). – Langdon Mar 01 '12 at 03:08

3 Answers3

29

Did some digging through the source and found that you're supposed to set event.dataTransfer.dropEffect = 'move'; inside your dragover event handler. Googled for dropEffect to read more and found:

dataTransfer.dropEffect

Controls the feedback that the user is given during the dragenter and dragover events. When the user hovers over a target element, the browser's cursor will indicate what type of operation is going to take place (e.g. a copy, a move, etc.). The effect can take on one of the following values: none, copy, link, move.

from: http://www.html5rocks.com/en/tutorials/dnd/basics/

Edit: Here's what I ended up with: http://jsfiddle.net/guYWx/16/

Had to do one additional trick to get it working perfectly. Did this so the dropper wouldn't appear when you select text and drag it around the page:

if ($.inArray('Files', e.dataTransfer.types) > -1)
philfreo
  • 41,941
  • 26
  • 128
  • 141
Langdon
  • 19,875
  • 18
  • 88
  • 107
4

@Langdon - Thanks for pointing out exactly what I needed! I have upvoted it.

After spending so many hours I got that suggestion working exactly as intended.

I made use of effectAllowed in combination with dropEffect to provide visual cues when performing drag drop operations. Completely cross-browser!

$(document).on('dragstart dragenter dragover', function(event) {    
    // Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
    if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
        // Needed to allow effectAllowed, dropEffect to take effect
        event.stopPropagation();
        // Needed to allow effectAllowed, dropEffect to take effect
        event.preventDefault();

        $('.dropzone').addClass('dropzone-hilight').show();     // Hilight the drop zone
        dropZoneVisible= true;

        // http://www.html5rocks.com/en/tutorials/dnd/basics/
        // http://api.jquery.com/category/events/event-object/
        event.originalEvent.dataTransfer.effectAllowed= 'none';
        event.originalEvent.dataTransfer.dropEffect= 'none';

         // .dropzone .message
        if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
            event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
            event.originalEvent.dataTransfer.dropEffect= 'move';
        } 
    }
}).on('drop dragleave dragend', function (event) {  
    dropZoneVisible= false;

    clearTimeout(dropZoneTimer);
    dropZoneTimer= setTimeout( function(){
        if( !dropZoneVisible ) {
            $('.dropzone').hide().removeClass('dropzone-hilight'); 
        }
    }, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
visitsb
  • 541
  • 4
  • 8
  • could you please post a working example, I am getting errors with undefined variables that I am not sure is your code or mine. – ctrlbrk Apr 04 '20 at 06:56
-3

You have to change cursor CSS property.

You find a list of the different values of cursor here.

You can also specify a custom cursor image with cursor: url('foo.png');.

Blender
  • 289,723
  • 53
  • 439
  • 496
InuYaksa
  • 726
  • 6
  • 10
  • I know I can change the cursor, but GMail is not doing this. There's huge a difference between cursor:no-drop and what the cursor looks like on GMail. – Langdon Dec 10 '11 at 22:47