20

Assumption: A local HTML/Javascript webpage that has access to file://

At the start of a drag on a draggable HTML element, in the event handler function dragStart(e), how do I add a File object so that it is recognized as a file and ends up in the dataTransfer.files list?

Ex:

function dragStart(e){
    var file = getSomeFileObjFromSomewhere();
    e.originalEvent.dataTransfer.effectAllowed = "all";
    e.originalEvent.dataTransfer.setData("file", file);

    console.log("\nFiles:");
    i = 0;
    var files = e.originalEvent.dataTransfer.files,
    len = files.length;
    for (; i < len; i++) {
        console.log("\nIndex: " + i + "\nFilename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
        console.dir(files[i]);
    }
}

Specifically, it needs to work on Chrome/Chromium. And, we can assume that the file exists on the local drive. Basically, I want the same data available then when a file is dragged from Windows Explorer to a HTML page on an element that is droppable.

I know that this exists in Chrome:

e.originalEvent.dataTransfer.setData("DownloadURL", fileType + ":" + name + ":" + filePath);

which downloads the file. But this is not what I want, because I want to assume that this is an existing file and that the original file must be accessed.

Kai
  • 326
  • 1
  • 3
  • 6
  • I doubt this can be done. If I understand correctly you're essentially asking the browser to upload someone's file without them initiating the file transfer. I can't see browsers allowing that to happen - it would be a security flaw – K Scandrett Mar 01 '17 at 03:09
  • If you are able to get file at `var file = getSomeFileObjFromSomewhere();` what is purpose of setting file object at `event.dataTransfer`? What are you trying to achieve? – guest271314 Mar 02 '17 at 07:38
  • 1
    @KScandrett - the File object would have to be generated from things already accessible to Javascript (including File objects generated explicitly by user interaction with a file input) – Fabio Beltramini Mar 02 '17 at 22:40
  • 1
    I am trying to achieve generating a file from one page that can be dragged by the user into another page, like they would drag a file from their file system into that other page – Fabio Beltramini Mar 02 '17 at 22:41
  • What do you mean by "another page"? [Drag and drop images, and not links, between windows - HTML5](http://stackoverflow.com/questions/41388434/drag-and-drop-images-and-not-links-between-windows-html5/)? – guest271314 Mar 02 '17 at 23:04
  • _"like they would drag a file from their file system into that other page"_ Do you mean drag and drop between two `html` documents? Or drag a file from user file manager UI at OS to an `html` document? – guest271314 Mar 02 '17 at 23:41
  • @FabioBeltramini Can you provide further description as to what are you trying to achieve? – guest271314 Mar 03 '17 at 03:43
  • From a dragstart event in one html page/document/window, attach a JS-generated file-object to the event in such a way that the user can drop it into another window, and that window recognizes the attached file, just as if the user had dragged it from their file system – Fabio Beltramini Mar 04 '17 at 18:14
  • In my scenario, the target page exists and listens for file drops, I can't edit it, I just want to make a tool for building and passing in compatible files – Fabio Beltramini Mar 04 '17 at 18:20
  • Example scenario: generate a CSV file using the "File" object, and store it in the drag object, as if the user dragged a file from their filesystem. This is useful to unify some workflows. – Eric Burel Jan 12 '23 at 09:15

2 Answers2

6

You can use the approach posted by @kol at Simulate drop file event

That is, we have to pass an argument to ondrop, which

  • has a dataTransfer field with a files array subfield, which contains the selected File, and
  • a preventDefault method (a function with no body will do).

adjusted below to attach .addEventListener("drop") to drop element at dragstart event, with File objects passed to a bound function with Function.prototype.bind() which returns the appropriate object described above, with once:true passed at third parameter to .addEventListener(), to call drop event at most once for each dragstart event where File objects are accessed or created.

FileList object is read only, an Array is used to store File object at dataTransfer.files property within a plain javascript object at event handlers.

Note: The FileList interface should be considered "at risk" since the general trend on the Web Platform is to replace such interfaces with the Array platform object in ECMAScript [ECMA-262]. In particular, this means syntax of the sort filelist.item(0) is at risk; most other programmatic use of FileList is unlikely to be affected by the eventual migration to an Array type.

If event.dataTransfer.files at dragstart event contains File objects, iterate FileList and push each File object to files array.

var drag = document.getElementById("drag")
var drop = document.getElementById("drop")

function handleDrop(evt) {
  evt.preventDefault();
  console.log(evt.dataTransfer.files);
}

function getSomeFileObjFromSomewhere() {
  var data = ["abc", "def"];
  var files = [];
  for (var i = 0; i < data.length; i++) {
    files.push(new File([data[i]], data[i] + ".text", {
      type: "text/plain",
      lastModified: new Date().getTime()
    }));
  }
  return files
}

function dataTransferFileObject(files) {
  return {
    preventDefault: function() {},
    dataTransfer: {
      files: Array.isArray(files) ? files : [files]
    }
  }
}

drag.addEventListener("dragstart", function dragStart(e) {

  var files = getSomeFileObjFromSomewhere();
  e.dataTransfer.effectAllowed = "all";

  console.log("\nFiles:");
  
  for (let i = 0; i < files.length; i++) {
    var {name, size, type} = files[i];
    console.log("\nFilename: " + name);
    console.log("Type: " + type);
    console.log("Size: " + size + " bytes");
  }
  // if `e.dataTransfer.files`, push `File` objects dragged
  // to `files` array
  if (e.dataTransfer.files) {
    for (let file of e.dataTransfer.files) {
      files.push(file);
    }
  }
  
  drop.addEventListener("drop"
  , handleDrop.bind(drop, dataTransferFileObject(files))
  , {once: true});

});

drop.addEventListener("dragover", function(evt) {
  evt.preventDefault()
});
div {
  width: 50px;
  height: 50px;
  padding: 10px;
  margin: 10px;
}

div:nth-child(1) {
  border: 2px dotted blue;
}

div:nth-child(2) {
  border: 2px dotted green;
}
<div draggable="true" id="drag">drag</div>
<div droppable="true" id="drop" webkitdropzone="webkitdropzone">drop</div>

plnkr http://plnkr.co/edit/ihQqs4t2zOg2XhIuNwal?p=preview

Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • Thanks, this seems like progress! It appears to work when viewed from within the dragstart event, but the dataTransfer items/files don't seem to persist through to the drop event. Or am I doing something wrong there? http://plnkr.co/edit/H5gfm82JZGRuEuDIM7WM?p=preview – Fabio Beltramini Mar 02 '17 at 22:38
  • @FabioBeltramini Note, the Question does not actually mention `drop` event. – guest271314 Mar 02 '17 at 22:47
  • Ok, regarding the drop event, perhaps I read too much into this phrase "I want the same data available [as] when a file is dragged from Windows Explorer to a HTML page on an element that is droppable". But lets see if this new plkr helps – Fabio Beltramini Mar 04 '17 at 18:16
  • The drop listener on the new pluker isn't checking the dataTransfer files. It's seeming like there is no way to attach files to the dataTransfer such that the drop event can receive them as files? – Fabio Beltramini Mar 04 '17 at 18:21
  • You can pass a data URI at setData and use getData at drop event. Though FileReader load event returns results asynchronously. – guest271314 Mar 04 '17 at 19:38
  • I will award you the bounty (as the only answer). Unfortunately, clever as this may be, it still doesn't help me, as I cannot modify the drop listener because it is on another page. – Fabio Beltramini Mar 06 '17 at 16:57
  • @FabioBeltramini _"Note: Dragging files can currently only happen from outside a [browsing context](https://www.w3.org/TR/2011/WD-html5-20110525/browsers.html#browsing-context), for example from a file system manager application."_ [7.7.5 Drag-and-drop processing model](https://www.w3.org/TR/2011/WD-html5-20110525/dnd.html#drag-and-drop-processing-model). Though you can use `.setData()`, `.getData()` approach to transfer `data URI` representation of file, see `console` at http://plnkr.co/edit/zmyRoA11W4iMl7m8TIPS?p=preview. Note also, approach at http://stackoverflow.com/a/41388593/ – guest271314 Mar 06 '17 at 17:10
  • @FabioBeltramini Have you considered posting a new Question including full context and requirements described at http://stackoverflow.com/questions/24496989/how-to-add-a-file-into-an-already-existing-datatransfer-object-using-javascript/42562239#comment72329852_24496989, http://stackoverflow.com/questions/24496989/how-to-add-a-file-into-an-already-existing-datatransfer-object-using-javascript/42562239#comment72329999_24496989 , http://stackoverflow.com/questions/24496989/how-to-add-a-file-into-an-already-existing-datatransfer-object-using-javascript/42562239#comment72390503_42562239 ? – guest271314 Mar 06 '17 at 17:25
  • Yes, I was planning on doing so once this bounty expired. However, your link to w3 helps to establish that the answer to my question would be "currently not possible". Thank you! – Fabio Beltramini Mar 06 '17 at 17:54
6

The simplest way is by "add item":

const dataTransfer = new DataTransfer();
const aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
dataTransfer.items.add(new File([new Blob(aFileParts, { type: 'text/html' })], 'test.txt'));

Only what u need it's adjust your file to file type :)

Verri
  • 1,580
  • 19
  • 22