Drag out files like Gmail

Google in their quest to keep me busy in trying to figure out how they do their innovative features in Gmail are at it again. First it was drag and drop uploading which used a clever trick to make it work in Chrome which currently doesn’t support the FileReader in their stable release. Now they’ve added the ability to drag out attachments to your file system, allowing you to bypass the usual method of the save dialog.

While the first feature of drag and drop uploading I figured out quite easily, this drag out feature was a doozy.

How did I figure it out?

There were two clues which got me started. One it only works in Chrome so it had to be an extension to the current Drag and Drop API. Two, after some poking around in gmail, there was a custom attribute on the attachment link called download_url which colon separated the attachments mime type, file name and download link.

Since Gmails JavaScript is obfuscated to within in an inch of its life there was no easy way to attach the built in debugger to anything that might give it away. So I tried downloading the script and running it through various unobfuscaters which made it format nicely but I still had to work with function names like vHG etc.

My last hope was chromiums bug tracker and searching to see if any bugs or feature requests were filed that could help give it away. I knew the download_url attribute played a role and it would be set using setData method on the dataTransfer object. So I searched high and low on Chromiums bug tracker for matches to “download_url”, “downloadurl” & “downloadurl setData” nothing nada, zip. So I turned to the webkit bug tracker, still nothing! So I thought maybe Mozilla had discussed it on their bug tracker, a long shot but worth a try. Bingo! This bug led me to this bug, on webkits bug tracker don’t ask why the search didn’t bring this up, and then onto this proposal. We’re in business!

How does it work?

So we’ve found the details let’s play with it.

Update: I’ve rolled out drag to desktop across my site for all my demo source files. If you’re using Chrome 5+ just drag the “Download the source files” link to your desktop!

The above demo will, in Chrome 5+, allow you to drag any of the items to your desktop and save them without having to go through the usual save dialog process.

var file = document.getElementById("dragout");
 
file.addEventListener("dragstart",function(evt){
    evt.dataTransfer.setData("DownloadURL",fileDetails);
},false);

From the code above you attach an ondragstart event listener to something you want to “drag out” and save to your file system. On the dragstart event you set the data using the new “DownloadURL” type and pass file information to it.

In order to pass the correct information to the event we access the download_url attribute and use that for our setData call. I’ve made one change that is slightly different to how Gmail sets and gets the attribute.

<a href="Eadui.ttf" id="dragout" draggable="true" data-downloadurl="
    application/octet-stream
    :Eadui.ttf
    :http://thecssninja.com/gmail_dragout/Eadui.ttf">Font file</a>

Instead of creating a new attribute I have instead used the new custom data attributes specified in the HTML5 spec. While not officially supported by Chrome yet we can still use it and add a simple test for support and fork the code either way.

The custom attribute needs three things specified that are separated by colons in order for it to work. The files mime type, the name you wish it to be saved as (this can be anything) and the url to where the file can be downloaded from.

var fileDetails;
 
if(typeof file.dataset === "undefined") {
    // Grab it the old way
    fileDetails = file.getAttribute("data-downloadurl");
} else {
    fileDetails = file.dataset.downloadurl;
}

Above I do a check to see if the element supports the dataset attribute if not use getAttribute to grab the value.

A cool finding

Playing around with this new functionality I did find a cool side effect that if I drag a file that’s set the DownloadURL type on the setData method into a page using Firefox 3.6+ that will capture a drop event the dataTransfer file attribute will act as though it’s captured a local file and can be manipulated with the FileAPI e.g. In my font dragr web app if I drag a font file that’s been attached to an email it will render the font as though I’ve dragged it from my local file system! Doing the same with Chrome 6, which also supports file attribute on the dataTransfer property, will ignore the drop.

Predicting the next challenge from Google

So what do I think the Gmail team will do next? Since they announced, and are now starting, their 6 week release cycle of chrome. I foresee the ability to upload folders not just individual files, being able to capture a photo from your web cam straight into an embedded picture using the proposed media capture proposal. Whatever it is I’m sure it’ll knock everyone’s socks off and make me rack my brain in trying to figure it out.

Short URL: http://cssn.in/ja/028

 

Post filed under: html5, javascript.

Skip to comment form.

  1. Nadav says:

    Very well done! You are a true thinker, keep up the good work!

  2. l4u says:

    seems that it works in firefox too

  3. @l4u – Firefox appears to work but what it’s really doing is creating a shortcut to the anchor link where ever you drag it out to on windows. Not sure what it does on other OS platforms.

  4. Jared says:

    Nice, this is pretty cool. I hate that damn save file dialog box.
    I didn’t even know you could do that in gmail.

  5. Great work, thanks! A quick test shows this also works for Data URLs, which thus offers an easy way to download client-generated files. Awesome!

  6. @Alexis – Nice, the canvas image editors could benefit from that.

  7. On firefox on ubuntu it works, and it downloads the file to the desktop. (nautilus does the download job, not the browser)

  8. David Tong says:

    Nice article! I am trying to apply this to my product, but having no success in dragging a div out of the browser window. I feel like dnd from browser to desktop is only applicable to <a> and <img>.

    What I tried to do is that when I drag a div item, I change the event object’s target/srcTarget properties to point to a hidden element that contains the data-downloadurl, but those properties (event.target/event.srcTarget) seem to be somewhat read-only.

    I cannot change the div item to <a> . Any suggestion on how I can drag this thing out without adding anything visual (such as an explicit download link or icon) to the UI?

    Thanks!

  9. @David T – That’s because anchor and img elements are natrually draggable, to enable it for other elements you need to add the draggable attribute and some CSS for webkit browsers.

    <div draggable="true"></div>

    and the CSS for webkit browsers.

    [draggable=true] {
        -khtml-user-drag: element;
        -webkit-user-drag: element;
    }
  10. Beben says:

    like usually, always write great article…
    its like a jutsu in ninja :P
    thanks a lot ☼ ○

  11. Warren says:

    Ninja, I submitted a bug to Chromium titled “dataTransfer setData multiple formats does not work in Chrome for Windows if one of the types is ‘DownloadURL’”

    http://code.google.com/p/chromium/issues/detail?id=55071&can=5&colspec=ID%20Stars%20Pri%20Area%20Feature%20Type%20Status%20Summary%20Modified%20Owner%20Mstone%20OS

    From your research on this topic, did you find a workaround for this issue?

  12. @Warren – No I haven’t come across that issue thanks for the heads up.

  13. Foss Tighe says:

    This is cool. I noticed that FF and IE allow drag and drop images to the desk top. I did a little test to see if that could be exploited to accomplish a wider drag out functionality.

    In FF 3.6 I was able place an image on a page using the name of a file I would ultimately like to dnd to the desktop. At render time it pointed to a gif (but was named myfile.txt). Image rendered fine. I also ensured the file was not cached by using server. I attached some synchronous AJAX code to the drag start that changed the content of myfile.txt on the server. Made it contain text.

    When the user clicks the image and DnD’s to the desktop, the file is re-loaded from the server and copied to the desktop. The result was a file named myfile.txt that contains text (not the original image).

    In IE 8.0 this did not work. I think because the file was not a known image mime type, IE would not allow me to drop it on the desktop.

  14. Anentropic says:

    I noticed that this works fine dragging to the desktop but dragging into another HTML page (I tried with http://www.html5rocks.com/tutorials/file/dndfiles/#toc-selecting-files-dnd ) doesn’t seem to work.

    I’m in Chrome on Windows… is this the bug @Warren is talking about above?

  15. @Anentropic – Dragging from Chrome to Firefox with the html5rocks demo loaded in Firefox will work like you are dragging a local file from your file system. This behaviour only seems to work in Firefox and not Chrome. It’s not the bug that @Warren is talking about.

  16. Anentropic says:

    @ninja but it doesn’t work for me, either chrome->chrome or chrome->firefox… it doesn’t work like dropping a local file. The drop page reacts, but with a blank content. This is different to if you first drag out of chrome onto the desktop and then from the desktop into the drop page.

  17. @Anentropic – It works for me from chrome 8 to FF3.6. I did a quick screencast to prove it http://screenr.com/StL this has worked for me on Vista and Win7. Haven’t tried Linux or Mac.

  18. Anentropic says:

    @ninja hmm, I’ll try again with your fontdragr page on Win 7 some time. With FF3.6 and Chrome 8 on OSX it still doesn’t work – the drop box highlights but doesn’t receive anything (and Chrome -> Chrome it doesn’t even highlight). Probably not your code, but obviously still some issues with the browsers on this feature!

  19. It could very well be a windows only side effect. Chrome to Chrome doesn’t work it’s only Chrome to Firefox that works for me. I’m using the propriety CSS psuedo-class :-moz-drag-over for the highlight on drag over in Firefox.

  20. Anentropic says:

    @ninja …just tried on Win 7, FF3.6.12, Chrome 8… it works! :)

  21. tariq says:

    is there a possibility to drag out multiple files at once? Any ideas on that?

  22. @tariq
    Not at the moment but that would a useful addition to fleshout for the Chromium team if they ever decide to spec the downloadURL.

  23. Derek says:

    Dragging out file from browser is easy, if you don’t want any script tag, you can do this.
    For example:

    <a href="http://www.google.com/logos/classicplus.png" 
        draggable="true" 
        ondragstart="event.dataTransfer.setData('DownloadURL',
        'image/png:classicplus.png:http://www.google.com/logos/classicplus.png');" 
        onclick="return false"
    >
    Drag me
    </a>

    Which do the same thing. (You can also use div instead of a)
    Read Google’s http://www.html5rocks.com/ for more info. :)

  24. Great article. Great comments.

    > and the CSS for webkit browsers.

    For other browsers, it is user-select: none;

    The entire rule set:

    [draggable] {
    -moz-user-select: none;
    -khtml-user-select: none;
    -webkit-user-select: none;
    user-select: none;
    }

    See you on Twitter: @4digitalmasters

  25. AJ ONeal says:

    Doesn’t work in Firefox 8 (FF8) on OS X Lion.

    Looks like they’ve dropped support. The only reference I could find is for extensions:

    Dragging files to an operating system folder

    https://developer.mozilla.org/User:venesa/Recommended_Drag_Types#Dragging_files_to_an_operating_system_folder

  26. Ryan Seddon says:

    @AJ ONeal

    This never worked for Firefox, only Chrome as they added support for the proprietry “downloadURL” type.

  27. iuwei says:

    Cool! Good Job!!

  28. Here’s a jQuery (1.7+) version of the script:

    $(function () {
      $("a[draggable]").on("dragstart", function(e){
        var dataset;
        if (this.dataset){
          dataset = this.getAttribute("data-downloadurl");
        } else {
          dataset = this.dataset.downloadurl;
        }
        e.originalEvent.dataTransfer.setData("DownloadURL",dataset);
      });
    });
    
  29. xDelph says:

    It works well for me but i have a problem

    I want to cancel a live drag with a timer (the user take too long to drop for example) but it doesn’t want to work (even the escape key press doesn’t work)

    can someone help me ?

    I use : last Chrome version only, HTML5, Javascript, jQuery

  30. Ian says:

    Is there a way to set it up so all links with the data-downloadurl attribute will download on drag instead of addressing each one individually with the links ID?

  31. Ryan Seddon says:

    @Ian

    You could just do a selector in jQuery or querySelectorAll to grab all anchor links with a data-downloadurl attribute and apply the drag out functionality that way.

  32. Luvy Gonzalez says:

    Hi!
    Does this work on IE?

  33. Joran Greef says:

    This is great news. I tested on Mac, trying to drag the PDF directly onto the Preview app in my dock but got an alert message: “The file “HTML5CheatSheet-2.pdf” could not be opened because it is empty.”

  34. Shrike says:

    Unfortunately it doesn’t work anymore.

  35. Bhanu says:

    Unfortunately it doesn’t work anymore in any browser, not also in chrome

Leave a comment