jQuery and jQuery UI has cool stuff that makes drag and drop easy with jQuery draggable and jQuery droppable if you want the droppable functionality. With draggable however, if the element or element’s child is capturing click events or an anchor tag with a link, it triggers the click event and follows the link after you are done dragging. After several researches, I gave up and devised a hackish solution.
The Markup
The HTML markup is very simple: I have a floor (a div container) and several markers (divs containing anchor tags). The floor will contain the markers and the markers represents the guests seated on certain position of the floor, where the floor has a background image of a typical bar layout.
When the guest wants to move to another table, we need to move the marker (which represents him and his companions).
And the HTML markup looks like this:
<div id="floor"> <div id="guest_xxx_yyy" class="marker" style="top: yyy; left: xxx; z-index: z"> <a href="link-to-guest-info-page"> </a> </div> <div id="guest_xxx_yyy" class="marker" style="top: yyy; left: xxx; z-index: z"> <a href="link-to-guest-info-page"> </a> </div> <div id="guest_xxx_yyy" class="marker" style="top: yyy; left: xxx; z-index: z"> <a href="link-to-guest-info-page"> </a> </div> </div>
From the markup above, the floor holds the markers. It is positioned relative. The markers via a class, is positioned absolute relative to the floor. The initial JavaScript code using jQuery looks like these:
$(function(){ $(".marker").draggable({ containment: "#floor", start: function(e, ui){}, stop: function(e, ui){ // move marker via ajax } }); });
It actually works right away. When you drag a marker, it is moved the the position where you dropped it inside the floor.
However, since the marker contains the anchor tag that has a valid link on it, the link is followed, opening the guest info page. There must be a way to prevent following the link by preventing the link event to trigger. I have come across several searches and some results on stackoverflow.com seems not suited for my problem. If I could capture the click event on the anchor tag, I can cancel the event, so I decided to solve it this way.
A Simple Trick
Since draggable events also consists of click event, it can interfere the click event which I don’t want during dragging. I decided to capture all click events on the link/anchor tag and hook it up with the stop event on draggable. I did this by having an evil global variable that decides whether to cancel following the link or not. Here is the final JavaScript code.
var cancelFollow = false; $(function(){ // decides whether to follow the link or cancel it $(".marker a").click(function(){ e.stopPropagation(); if (cancelFollow) { cancelFollow = false; return false; } return true; }); $(".marker").draggable({ containment: "#floor", start: function(e, ui){}, stop: function(e, ui){ // tell everyone to cancel following the marker link cancelFollow = true; // move marker via ajax } }); });
That’s it!
Why not have the drag code put a specific class into the object being dragged then have the click handler check for that class and cancel the click event if it’s there (then remove the class name).
That way, there’s no need for a global var.
Hi dave e, I tested your advice but it seems that is does not work as expected. The dragging class is ui-draggable-dragging, however, when the click event is triggered, the ui-draggable-dragging was removed already as if the dragging event has already finished its job.
It seems that the click event fires after the stop event of ui-draggable (not before). Thus, the default class ui-draggable-draggin has been cleanup.
Though it is possible to create a class ui-draggable-dragged to indicate that the marker has been recently dragged, then capture the even of the child, which in turn cleans it up.
I’ll try it.
Hi dave e again,
I have tested my code. At the stop drag stop event of jQuery draggable, I added:
$(this).addClass("ui-draggable-dragged");
which adds class that indicated that the marker has been recently dragged. To clean-up the class, a click event will catch the child link’s click and have this code:
$(".occupant-marker a").click(function(e){
e.stopPropagation();
var parent = $(this).parents('div');
if (parent.hasClass("ui-draggable-dragged"))
{
parent.removeClass("ui-draggable-dragged");
return false;
}
return true;
});
It will remove the class if it exists, to clean it up and cancel the click event. Sorry for being verbose, I’m just taking notes 😀
Thank you for promoting clean code
Thanks a lot for this code, a clever hack that came in handy. Much appreciated!
In my case both examples above don’t work (at least in FF), I modyfied a little first function:
$(".marker a").click(function (e) {
if (cancelFollow) {
cancelFollow = false;
e.stopImmediatePropagation();
return false;
}
return true;
});
and after that change it started working. But to be honest I don’t understand actually why.
Hi Macrin,
What examples are you talking about? The original post or the comments? I have not updated the original post (laziness perhaps) but I have applied changes mentioned in my comments above. Maybe I have not posted them completely but it works in IE down to IE6 and other modern browsers like Firefox, Opera, Safari (Windows) and Google Chrome.
That
e.stopImmediatePropagation()
is new to me, have to check it out.Thanks