firefox ondrop event.dataTransfer is null after update to version 52 - javascript

Introduction:
At about mid March 2017, after an update of firefox to version 52, certain functions - drop and paste - ceaced to function properly. As it shows up on debugging, the attribute "dataTransfer" of the event is nowadays set to null.
Previously to the update, onDrop and onPaste events both delivered the dataTransfer attribut set to the contents of what were to be dropped or pasted.
Questions:
How should drop and paste be handled with actual browsers?
Are there any precautions necessary these days?
Is there an explanation out there of the reasons behind the restrictive behavior of nowadays?
Is there any example out there in the internet showing how to accomplish the task with actual browsers?
I do not ask for examples prior to version 48 of firefox, since at least until that version, the whole thing worked flawlessly. I do not ask for examples with jQuery or any other library (while not rejecting those if they come as additional supplements). I do ask for examples with simple plain native javascript.

When debugging step by step, the data from dataTransfer seems to get lost. Probably because of the events involved in debugging. Start the step-by-step debugging after the reading of dataTransfer (ev.dataTransfer.getData), and you will see that dataTransfer is not null anymore.

With the latest version of FF (currently 73.0.1) dataTransfer appears as null only when debugging the drop event handler (that is, via breakpoint or debugger statement). Not debugging the function makes it work properly.

I came across with similar issue.
We need to set few properties value to dataTransfer.
for example set eventObj.dataTransfer.effectAllowed = "move"; along with eventObj.dataTransfer.setData("text", "data"); in your dragstart function.
function dragStart(eventObj) {
eventObj.dataTransfer.setData("text", "data");
eventObj.dataTransfer.effectAllowed = "move";
}
you have to prevent default action of an element from happening. by using eventObj.preventDefault(); and need to set eventObj.dataTransfer.dropEffect = "move"; in your dragover function like.
function dragOver(eventObj) {
eventObj.preventDefault();
eventObj.dataTransfer.dropEffect = "move";
}
In drop function also you have to stop default action otherwise firefox will open new page with whatever data you have passed in dataTransfer api.
function drop(eventObj) {
vardata = eventObj.dataTransfer.getData("text");
eventObj.preventDefault();
}
I hope it will solve your issue in firefox.
Happy coding

I had the same issue starting from the code on this page: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop: the dataTransfer object was null so I was not able to access the dropped files: event.dataTransfer.items;
I have fixed the issue extracting the dropped files on the area before calling ev.preventDefault();
Here my modified method:
function dropHandler(ev) {
console.log('File(s) dropped');
const _files = ev.dataTransfer.items;
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
if (_files) {
// Use DataTransferItemList interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[i].kind === 'file') {
const file = ev.dataTransfer.items[i].getAsFile();
console.log('... file[' + i + '].name = ' + file.name);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (const i = 0; i < ev.dataTransfer.files.length; i++) {
console.log('... file[' + i + '].name = ' + ev.dataTransfer.files[i].name);
}
}
}

I just changed drag event with dragstart
drag.addEventListener('dragstart', e => {
e.dataTransfer.setData("key", "val");
})

Related

object.onload fails with IE and Edge

I'm trying to dynamically preload list of files which may be anything between images and JavaScript files. Everything is going supersmooth with Chrome and Firefox, but failing when I'm trying to preload JavaScript files with Edge. Edge still can handle images for example but no js files. And yes I've tried with addEventListener, it's not working either.
Edge doesn't give me any errors.
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = path/to/javascriptfile.js
body.appendChild(object);
object.onload = function(){
console.log('hello world')
//not firing with edge
}
Anything relevant I'm missing?
UPDATE: Didn't get any success after the day. Will probably leave it for now and just skip preloading script files with edge until i find a solution.
Perhaps worth a check - from msdn:
The client loads applications, embedded objects, and images as soon as
it encounters the applet, embed, and img objects during parsing.
Consequently, the onload event for these objects occurs before the
client parses any subsequent objects. To ensure that an event handler
receives the onload event for these objects, place the script object
that defines the event handler before the object and use the onload
attribute in the object to set the handler.
https://msdn.microsoft.com/en-us/library/windows/apps/hh465984.aspx
Edit, a clarification:
You should attach the event listener before the element is added to the page.
Even doing that I'm not sure if it'll work or not though. But to make sure you've exhausted all options try the example below:
function doLoad() {
console.log('The load event is executing');
}
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = 'path/to/javascriptfile.js';
object.onreadystatechange = function () {
if (object.readyState === 'loaded' || object.readyState === 'complete') doLoad();
console.log('onreadystatechange');
}
if (object.addEventListener) {
object.addEventListener( "load", doLoad, false );
console.log('addEventListener');
}
else
{
if (object.attachEvent) {
object.attachEvent( "onload", doLoad );
console.log('attachEvent');
} else if (object.onLoad) {
object.onload = doLoad;
console.log('onload');
}
}
var body = document.getElementsByTagName("body")[0];
body.appendChild(object);
If this doesn't work, you could perhaps preload using "image" instead of "object" in IE: https://stackoverflow.com/a/11103087/1996783
Working temporary solution is to put onload event directly to script element instead of object. It's sad since it works like a charm in Chrome & FF.
It turns out, object.data with css source did not load either. I don't know if it's a bug since it still can load image from to object.data.
But show must go on.
Cheers, eljuko

Trigger 'drop' event while providing the file data

Question
How could I, while providing the file, trigger a drop event of a field, on which I do not have access at loading.
Details
There is a page with a field on which is attached a drop listener that process an image when dropped. I would like to be able to use this process by pasting an image. I know how to get the file from a paste, but I do not know how to dispatch a drop event that would contains this very file.
The obstacles are:
The code is obfuscated, I cannot access the function linked with the listener by name.
There is no way to get the drop listener after it being attached to an element. It seems there is some way to do it in the console, but not from a script.
I do not control the page rendering; i.e. I cannot intercept the event listener addition.
Vanilla Javascript & could only work in Chrome (extension).
This page is built in vanilla; i.e. no jQuery or anything.
Does anyone have an idea on how to tackle this task?
I am looking into DragEvent but "although this interface has a constructor, it is not possible to create a useful DataTransfer object from script, since DataTransfer objects have a processing and security model that is coordinated by the browser during drag-and-drops."
I saw a possible approach https://stackoverflow.com/a/39066443/1004274 but I want to mimic a real drop event with its data, i.e. pass a file I got via clipboardData.items[0].getAsFile(); instead of just text.
You can fake the drop event, and fake pretty much everything that's in there. What you'll have problem doing is triggering a default event, such as opening a file in a tab by dropping it. The reason isn't so much because of the dataTransfer object being protected, but the event not being trusted. By having trusted event and protected dataTransfer, you can be sure you won't pass data to a trusted event, and that you won't trigger default event with unwanted data.
But depending on how the drop function is accessing the file that is dropped, you might be able to trick it with a fake drop event and a fake dataTransfer object. See this fiddle for a general idea of how it may work:
var a = document.getElementById('link');
var dropZone1 = document.getElementById('dropZone1');
var dropZone2 = document.getElementById('dropZone2');
var fakeDropBtn = document.getElementById('fakeDropBtn');
dropZone1.addEventListener('dragover', function(e) {
e.preventDefault();
});
dropZone2.addEventListener('dragover', function(e) {
e.preventDefault();
});
dropZone1.addEventListener('drop', function(e) {
// This first drop zone is simply to get access to a file.
// In your case the file would come from the clipboard
// but you need to work with an extension to have access
// to paste data, so here I use a drop event
e.preventDefault();
fakeDropBtn.classList.remove('disabled');
dropZone2.classList.remove('disabled');
var fileToDrop = e.dataTransfer.files[0];
// You create a drop event
var fakeDropEvent = new DragEvent('drop');
// You override dataTransfer with whichever property
// and method the drop function needs
Object.defineProperty(fakeDropEvent, 'dataTransfer', {
value: new FakeDataTransfer(fileToDrop)
});
fakeDropBtn.addEventListener('click', function(e) {
e.preventDefault();
// the fake event will be called on the button click
dropZone2.dispatchEvent(fakeDropEvent);
});
});
dropZone2.addEventListener('drop', function(e) {
e.preventDefault();
// this is the fake event being called. In this case for
// example, the function gets access to dataTransfer files.
// You'll see the result will be the same with a real
// drop event or with a fake drop event. The only thing
// that matters is to override the specific property this function
// is using.
var url = window.URL.createObjectURL(e.dataTransfer.files[0]);
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
});
function FakeDataTransfer(file) {
this.dropEffect = 'all';
this.effectAllowed = 'all';
this.items = [];
this.types = ['Files'];
this.getData = function() {
return file;
};
this.files = [file];
};
https://jsfiddle.net/5m2u0tux/6/

e.dataTransfer.files.length showing up as 0

I am doing a pretty standard drag and drop feature in HTML5. However, for some reason e.target.dataTransfer showing up as undefined. I am new to javascript. Any help will be appreciated.
This is the java script.
<script src="jquery-2.1.4.min.js"></script>
<script>
$(document)
.ready(
function() {
jQuery.event.props.push('dataTransfer');
if (!!window.FileReader) {
// Browser suppports FILE API.
// is XHR2 available?
var xhr = new XMLHttpRequest();
if (xhr.upload) {
$('#drag-and-drop-zone').bind('dragover',
function(e) {
e.preventDefault();
e.stopPropagation();
$(this).addClass('drag-over');
return false;
});
$('#drag-and-drop-zone').bind('dragleave',
function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass('drag-over');
return false;
});
$('#drag-and-drop-zone')
.bind(
'drop',
function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass(
'drag-over');
alert(this.id);
alert('This shows up as 0. Why? . '
+ e.dataTransfer.files.length);
return false;
});
}
} else {
// Browser does not support FILE API.
$('#drag-and-drop-zone')
.html(
'Upgrade your browser. Find something that can handle HTML5.');
}
});
</script>
And this is the HTML
<body>
<div id="drag-and-drop-zone">Drag and drop the organization data
file on this area.</div>
</body>
When I run this - somehow the file drop is registered, as in the correct events are triggered. But somehow the number of files show up as 0. Please help.
Very interesting!
Add the following lines to the drop function you have - in front of your alert messages.
var files = e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
console.log('f: ', f);
}
You'll notice your alert no longer says 0. When I dragged a single file, it showed 1 (as expected). It also showed the proper count when I dragged multiple files.
I'm no expert of the internals, but when I looked at the FireBug console, I noticed the FileList was not presented as a regular type of object. I suspect that the File API relies on "Just In Time" delivery of content. It makes sense, why carry around the overhead of all the data when the drop container may not properly handle it.
Once the data is requested (with the for loop), the File API populates the appropriate internal data structures for use by the calling code.
Notice that the for loop doesn't loop through a count - it actually requests a file at the given index. The File API returns false if the file is not available. That is what actually causes the file to get loaded for use by your code - as well as cancel the for loop when there are no more files.
While I can't specifically answer your question of "why", at least this can get you moving along. Once you've looped through the available files, you can make use of the FileList length (and any other data that you need).
I hope this helps.
Good Luck!
Ok. Got it. The issue was with the version of Firefox I was using. I tried with IE11 and it is working fine now. I have failed to check for the support of FileApi.

HTML5 DnD dataTransfer setData or getData not working in every browser except Firefox

Consider this JSFiddle. It works fine in Firefox (14.0.1), but fails in Chrome (21.0.1180.75), Safari (?) and Opera(12.01?) on both Windows (7) and OS X (10.8). As far as I can tell the issue is with either the setData() or getData() methods on the dataTransfer object. Here's the relevant code from the JSFiddle.
var dragStartHandler = function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
};
var dragEnterHandler = function (e) {
// dataTransferValue is a global variable declared higher up.
// No, I don't want to hear about why global variables are evil,
// that's not my issue.
dataTransferValue = e.originalEvent.dataTransfer.getData("text/plain");
console.log(dataTransferValue);
};
As far as I can tell this should work perfectly fine and if you look at the console while dragging an item you will see the id written out, which means that it's finding the element just fine and grabbing it's id attribute. The question is, is it just not setting the data or not getting the data?
I'd appreciate suggestions because after a week of working on this with three attempts and some 200+ versions, I'm starting to loose my mind. All I know is it used to work back in version 60 or so and that specific code hasn't changed at all...
Actually, one of the major differences between 6X and 124 is that I changed the event binding from live() to on(). I don't think that's the issue, but I've come to see a couple failures from Chrome when it comes to DnD while working on this. This has been debunked. The event binding method has no effect on the issue.
UPDATE
I've created a new JSFiddle that strips out absolutely everything and just leaves the event binding and handlers. I tested it with jQuery 1.7.2 and 1.8 with both on() and live(). The issue persisted so I dropped down a level and removed all frameworks and used pure JavaScript. The issue still persisted, so based on my testing it's not my code that's failing. Instead it appears that Chrome, Safari and Opera are all implementing either setData() or getData() off spec or just failing for some reason or another. Please correct me if I'm wrong.
Anyway, if you take a look at the new JSFiddle you should be able to replicate the issue, just look at the console when you're dragging over an element designated to accept a drop. I've gone ahead and opened a ticket with Chromium. In the end I may still be doing something wrong, but I simply don't know how else to do DnD at this point. The new JSFiddle is as stripped down as it can get...
Ok, so after a bit more digging around, I found that the problem actually isn't with Chrome, Safari, and Opera. What gave it away was that Firefox was supporting it and I just couldn't say the other browsers are failing, since that's something I'd normally accept for IE.
The real cause of the issue is the DnD specification itself. According to the spec for the drag, dragenter, dragleave, dragover and dragend events the drag data store mode is protected mode. What is protected mode you ask? It is:
For all other events. The formats and kinds in the drag data store
list of items representing dragged data can be enumerated, but the
data itself is unavailable and no new data can be added.
That translates to, "you have no access to the data that you set, not even in read only mode! Go f#&# yourself.". Really? Who'se the genius that came up with this?
Now, to get around that limitation you have few choices, and I'm only going to outline two that I've come up with. Your first one is to use an evil global variable and pollute the global namespace. Your second choice is to use the HTML5 localStorage API to perform the EXACT same functionality that the DnD API should have provided to begin with!
If you go down this route, which I have, you're now implementing two HTML5 APIs not because you want to, but because you have to. Now I'm starting to appreciate PPK's rant about the disaster that the HTML5 DnD API is.
The bottom line is this, the spec needs to be changed to allow for access to the stored data even if it's only in read only mode. In my case, with this JSFiddle, I'm actually using the dragenter as a way to look ahead at the drop zone and verify that I should allow a drop to occur or not.
In this case Mozilla apparently opted out of full compliance with the spec which is why my JSFiddle was working just fine in it. It just so happens that this is the one time I fully support not supporting the full specification.
There is a reason for the "protected" bit....drag/drop can span completely different windows, and they didn't want somebody to be able to implement a "listener" DIV that would eavesdrop on the content of everything that was dragged over it (and maybe send those contents by AJAX to some spy server in Elbonia). Only the DROP area (which is more clearly under the user's control) gets the full scoop.
Annoying, but I can see why it might be considered necessary.
var dragStartHandler = function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
};
The problem is with the "text/plain". The standard specification in MSDN documentation for setData is just "text" (without the /plain). Chrome accepts the /plain, but IE does not, in any version I tried.
I struggled with the same problem for several weeks, trying to figure out why my "drop" events weren't firing properly in IE while they did in CHrome. It was because the dataTransfer data hadn't been properly loaded.
I know you already answered this, but this is a useful thread -- I just wanted to add an addendum here -- if you're setting the data yourself, you can always add the data into the field itself (ugly I know), but it prevents having to re-create functionality:
For instance, if setting your own custom data:
dataTransfer.setData('mycustom/whatever', 'data');
append the data as a new data entry, and iterate:
dataTransfer.setData('mycustom/whatever/data/{a json encoded string even}');
querying:
// naive webkit only look at the datatransfer.types
if (dataTransfer.types.indexOf('mycustom/whatever') >= 0) {
var dataTest = 'mycustom/whatever/data/';
// loop through types and create a map
for (var i in types) {
if (types[i].substr(0, dataTest.length) == dataTest) {
// shows:
// {a json encoded string even}
console.log('data:', types[i].substr(dataTest.length));
return; // your custom handler
}
}
}
tested in chrome only
Something also worth noting is that if you leave the execution chain using a timeout, the dataTransfer object won't have your data anymore.
e.g.
function dropEventHandler(event){
var dt = event.dataTransfer.getData("text/plain"); // works
var myEvent = event;
setTimeout(function(){
var dt = myEvent.dataTranfer.getData("text/plain"); // null
}, 1);
}
I was getting same error for below code:
event.originalEvent.dataTransfer.setData("text/plain",
event.target.getAttribute('id'));
I Changed code to:
event.originalEvent.dataTransfer.effectAllowed = "move";
event.originalEvent.dataTransfer.setData("text", event.target.getAttribute('id'));
And it worked for me.
I came across this post because I was having a similar experience with Chrome's dataTransfer.setData() and dataTransfer.getData() functions.
I had code that looked something like this:
HTML:
<div ondragstart="drag(event)" ondrop="newDrop(event)"></div>
JAVASCRIPT:
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function newDrop(ev){
var itemDragged = ev.dataTransfer.getData("text");
var toDropTo = ev.target.id;
}
The code worked perfectly fine in Internet Explorer but when it was tested in Chrome, I was unable to get values set in my dataTransfer object (set in drag function) using the dataTransfer.getData() function in the newDrop function. I was also unable to get the id value from the statement ev.target.id.
After some digging around on the web, I discovered that I was suppose to use the event parameters currentTarget property rather than the events target property. Updated code looked something like this:
JAVASCRIPT:
function drag(ev) {
ev.dataTransfer.setData("text", ev.currentTarget.id);
}
function newDrop(ev){
var itemDragged = ev.dataTransfer.getData("text");
var toDropTo = ev.currentTarget.id;
}
With this change I was able to use the dataTransfer.setData() and dataTransfer.getData() functions in chrome as well as internet explorer. I have not tested anywhere else and I am not sure why this worked. Hope this helps and hopefully someone can give an explanation.
I was working on a website testing with Firefox.
In WAMP on my laptop, code like the OP's worked. However, when I moved the website to HOSTMONSTER, it didn't work there.
I followed Joshua Espana's answer, and it resolved my problem.
failed:
ev.dataTransfer.setData("text/plain", ev.target.id);
worked:
ev.dataTransfer.setData("text", ev.currentTarget.id);
Thank, Joshua!
Anything pased to the dataTransfer only becomes available on ondrop events but ONLY on ondrop events (I believe this is a security consideration to prevent data being exposed to nefarious elements during a drag).
If you try adding an ondrop handler you should see the data exposed. Well at least you would if there weren't for one final trick...
To get the drop event to fire you need call .preventDefault on the dragover event or it prevents the drop event from firing
HTML (Angular)
<div (dragstart)="handleDragStart($event)"
(dragover)="handleDragover($event)"
(dragend)="handleDragEnd($event)"
(drop)="handleDrop($event)">
<div class="sortItem">item 1</div>
<div class="sortItem">item 2</div>
<div class="sortItem" draggable="true">Draggable</div>
<div class="sortItem">item 4</div>
</div>
Handlers (Typescript)
handleDragStart(event: DragEvent){
event.dataTransfer?.setData("text", '{"some": "data"}')
console.log('dragstart data:', event.dataTransfer?.getData("text"))
}
handleDragover(event: DragEvent) {
console.log('dragover data:', event.dataTransfer?.getData("text") || 'none')
event.preventDefault()
}
handleDragEnd(event: DragEvent) {
console.log('drag end data:', event.dataTransfer?.getData("text") || 'none')
}
handleDrop(event: DragEvent) {
console.log('drag drop data:', event.dataTransfer?.getData("text") || 'none')
}
Output from the above
If you drop the item INSIDE the container with the ondrop handler
If you don't cancel the dragover event...
If you drop the item OUTSIDE container with the ondrop handler
If you don't cancel the dragover event...
Other relevant SO questions
SO: Data only available on drop
SO: Drop event not firing
I also faced with this problem but no way worked for me when I set the data in ondrag function and get it back it gives the data but when try in other events like dragover and drop it dose not work.
I went this way maybe its not a proper answer but may help some one
let selectedId = 0;
function onDrag(params) {
// event.dataTransfer.setData("text",params);
selectedId = params
}
function onDragOver(params) {
// event.preventDefault()
// const data = event.dataTransfer.getData("text");
let id = selectedId
}

javascript: How to debug keyboard events

Imagine a web application which uses custom keyboard event handlers which might do event bubbling - or event catching.
Is there a way (e.g. Firefox/Firebug addon) to debug each keystroke/keyboard event, something like:
displaying the event type and all attributes
trace which javascript method had been called
in case of event bubbling which further methods have been called
To clarify my question: I don't know which method handlers exist and where they are defined - this is what I am trying to find out.
You could try to visualize the vents with the Firebug + Eventbug extension.
For a general overview on keyboard events in different browsers, try this: http://unixpapa.com/js/key.html
In IE you can use the debugger; keyword. Not sure about the x-browser friendliness:
function sayHello() {
debugger; // will break here and launch script debugging in IE
alert('hello world');
}
In the context of your challenge:
function someKeyPress(e) {
debugger;
// inspect e.keyCode ... etc
}
I find this to be the most effective debugging technique, but then again I spend a lot of time in IE. Many folks are comfy with Firebug but I find it cumbersom and far less intuitive than IE's debugger. IE's debugger provides for easier watches and expression evaluation, and also provides interactive reflection based tooltips (like VS debugger).
Also, per your question "trace what method was called" - the call stack is very responsive and easy to follow back/up.
UPDATE:
Here's a script to place on the bottom of each page to trap and debug events, in IE:
<script type="text/javascript">
function wrapIfHandled(el, evt) {
if (el && evt && el['on' + evt]) {
el['_on' + evt] = el['on' + evt];
el['on' + evt] = function (e) {
foo(e, el['_on' + evt]);
};
}
}
function wrapAll() {
var allEl = document.getElementsByTagName("*");
for (var i = 0; i < allEl.length; i++) {
wrapIfHandled(allEl[i], 'click');
// wrapIfHandled(allEl[i], other event names <keyup, keydown, etc>
}
}
function foo(e, d) {
debugger;
d(e);
}
wrapAll();
</script>
I don't know any extensions for this purpose. But, you can write handlers for key events and then print appropriate output to the javascript console. Also you can dump traces too. Firebug has builtin trace functionality: console.trace(). You can google with js trace keywords to find some trace tools too.
This page is a good example to handle keyboard events.

Categories