Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery: Changing the DOM on dragstart event fires dragend immediately?

i came across a bug for Chrome and Opera and i would like to know if its known and if so, is there a solution?

If i change the DOM on the dragstart event it immediately fires the dragend event?! Is this a bug or is there some reason behind it? Only happens in Chrome and Opera. Firefox works.

I appreciate every answer.

$('body').on({
      dragstart: function(e) {
        
        dragProfilefieldSrcElformid = $(this).attr("data-profilefieldid-formid");
        e.dataTransfer = e.originalEvent.dataTransfer;
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/html', $(this).attr("data-profilefieldid"));
        
        // Changing the DOM, fires the dragend Event in Chrome?
        $("#plugin_loginlogout_pfcontainer_" + dragProfilefieldSrcElformid).find(".plugin_loginlogout_pf_entryfield").addClass("highlight"); // This doesn't work in Chrome and Opera, but in Firefox
      },
      dragend: function() {
        console.log("dragend");
      }
      ".plugin_loginlogout_pf");

Edit:

Putting the DOM Change in a setTimeout Function seems to solve the problem!

like image 825
user3681084 Avatar asked Feb 09 '15 11:02

user3681084


1 Answers

It seems that different browsers manifest different behaviors towards long running operations.

JavaScript has a single thread that runs all of your instructions in the same queue. Each queue item is run in sequence and once the item has finished execution, the next item (from the queue) is grabbed and run.

The culprit for long running operation is the change you try to bring to the DOM (which I assume is preceded by a heavy search using find() that will run the DOM manipulation for each matched element).

What happens as you drag the element is that, all lines of code in the dragstart handler, and as you stop dragging, the dragend handler are pushed to the message queue respectively in order to be executed serially. However the DOM manipulation is taking more time (probably a few milliseconds more) to execute than the execution of the dragend handler before you stop dragging, and therefore, it appears as if the dragend fired way too soon.

Note: Sometimes block(s) of code create a new event and hence are pushed to the end of the browser event queue (or maybe somewhere after the item that's being run), resulting in a later execution. (I suppose the nature of it differs from browser to browser.)

The DOM manipulation part of your code might face such an issue in Chrome and Opera, though I'm not sure.

The setTimeout(fn, 0) Trick

The workaround for such situations is to wrap the long running operation block in a setTimeout function with 0 time.

(You can think of this as telling the browser to run the part of your code, in no time at all!, not literally though.)

Once a block of code has done execution, the browser will search for the available items waiting to be run, and the ones with setTimeout or setInterval will be pushed to the queue upon first available moment.

In your particular case, the trick is that setTimeout defers the execution of DOM change to a later time (at least 0 seconds) than the dragend event handler, thereby giving the impression as if the dragend event fired after the DOM change.

There is a great post by @DVK here explaining why setTimeout(fn, 0) is sometimes useful. Do check the JSfiddle by him (in Chrome) as well.

Update

As pointed by @MojoJojo and @Pradeep, it seems that Webkit browsers (older versions of Chrome in particular) have an issue with drag events. However, I tried to reproduce the bug in Chrome Version 47.0.2526.106 (newest version as of 11th January 2016), and the drag events fired without any irregularities.

Anyway, even if there was a bug, the setTimeout trick still applies as a proper workaround for the issue.

like image 51
Ahmad Baktash Hayeri Avatar answered Oct 18 '22 20:10

Ahmad Baktash Hayeri