Saturday, July 10, 2010

How to click on a drag/sort object

I've seen this question come up a few times on Stackoverflow. How can I make a draggable/sortable/resizable/selectable object clickable as well?

You can use the built in event timestamp to determine the time difference between when the mouse down and mouse up events occur. Here is a basic demo of a sortable list that contains hidden content - clicking on the header "Content #" will open the hidden below. Holding the mouse down on the header will allow you to sort the list.

HTML
This is a basic list which contains a header (h3) and a hidden div.
<ul id="sortable">
<li>
<h3>Content 1</h3>
<div class="hidden">Some Content</div>
</li>
<li>
<h3>Content 2</h3>
<div class="hidden">Some Content</div>
</li>
<li>
<h3>Content 3</h3>
<div class="hidden">Some Content</div>
</li>
<li>
<h3>Content 4</h3>
<div class="hidden">Some Content</div>
</li>
<li>
<h3>Content 5</h3>
<div class="hidden">Some Content</div>
</li>
</ul>
CSS
This css sets the styling of the list and hidden content.
/* set list width & remove bullets */
#sortable {
width: 500px;
list-style: none;
}
/* clickable header */
h3 {
font-size: 20px;
width: 200px;
background: #444;
margin: 5px;
padding-left: 5px;
}
/* header with revealed content */
h3.opened {
background: #0080c0;
}
/* hidden content */
.hidden {
display: none;
min-height: 100px;
background: #444;
color: #ddd;
padding: 5px;
}
Script
I've included a lot of inline comments in the script which I hope makes it clear as to what it does.
Updated code (9/24/2010 - Demo) - added mouse move flag:
$(document).ready(function(){
var last, diff, moved,
clickDelay = 500; // millisecond delay
$('#sortable')
// make the list sortable
.sortable()
// make h3 in the list "clickable"
.find('h3')
.bind('mousedown', function(e){
// set time on mousedown (start of click)
last = e.timeStamp;
// clear moved flag
moved = false;
})
.bind('mouseup mousemove', function(e){
    // flag showing that the mouse had been moved
    if (e.type == 'mousemove') {
     moved = true;
     // no need to continue
     return;
    }
// find time difference on mouse up (end of click)
diff = e.timeStamp - last;
    // check moved variable, to ignore click if the mouse had been moved (dragged)
    // if time difference is less than delay, then it was a click
    // if time difference is greater than the delay, then it was meant for dragging
if ( !moved &&  diff < clickDelay ) {
// toggle the opened class and the hidden content
$(this).toggleClass('opened').next().toggle();
}
});
});
Original code - Demo:
$(document).ready(function(){
var last, diff,
clickDelay = 500; // millisecond delay
$('#sortable')
// make the list sortable
.sortable()
// make h3 in the list "clickable"
.find('h3')
.bind('mousedown', function(e){
// set time on mousedown (start of click)
last = e.timeStamp;
})
.bind('mouseup', function(e){
// find time difference on mouse up (end of click)
diff = e.timeStamp - last;
// if time difference is less than delay, then it was a click
// if time difference is greater than the delay, then it was meant for dragging
if ( diff < clickDelay ) {
// toggle the opened class and the hidden content
$(this).toggleClass('opened').next().toggle();
}
});
});
  • In case you don't understand what's going on, a click is basically composed of a mousedown event and a mouseup event. The time difference between the two events is how you can determine if you have clicked or just pressed the mouse button to select text or drag an object around (like in this demo).
  • So in this script, the "last" variable contains the time stamp of the mouse down event
  • The "diff" is the time difference from the mouseup event and the last variable time.
  • I picked a default of 500 milliseconds for a mouse click delay (reference) for really slow clickers, but you can adjust it to anything you want.
  • If the time difference is less than 500 milliseconds then we can determine that the user clicked and preform the appropriate action (toggle the hidden content).
  • If the time difference is greater, then the user is probably trying to sort the list.
  • You could potentially add another action if the time difference is greater than the delay, but the script wouldn't do anything until the user released the mouse (mouseup event). But then you could bind a "mousemove" event that checks the timer against the "last" variable to find out how long the user has been clicking the mouse.
  • Update (9/24/2010): The newer script added above the original adds a mouse move flag that ignores the timer if the user moved the mouse during the click.