Sortable table rows with jQuery – Draggable rows
A couple of days ago I explained how I added controls to a table that when clicked, moved a table row (TR element), up and down within a table.
You can take a look at the solution for that problem by reading “Sortable table rows with jQuery“.
As so often happens with the code I write, I saw an opportunity to tweak and refine it. I was prompted to make these changes when a colleague complained that one should be able to drag and drop the rows within the table.
Again I found that the problem wasn’t so hard to solve as I first imagined. Read on and I will show how I achieved it.
Draggable rows with jQuery
Once again, it all started with a table:
<table>
<thead>
<tr>
<th>Position</th><th>Name</th><th>Colour</th><th>An extra column</th>
</tr>
</thead>
<tbody>
<tr class="row1">
<td>1</td><td>Geraldo</td><td>Pink</td><td> Extra content</td>
</tr>
<tr class="row2">
<td>2</td><td>Lorenzo</td><td>Green</td><td> Extra content</td>
</tr>
<tr class="row1">
<td>3</td><td>Flamingo</td><td>Blue</td><td> Extra content</td>
</tr>
<tr class="row2">
<td>4</td><td>Denya</td><td>Red</td><td> Extra content</td>
</tr>
</tbody>
</table> |
The astute reader may notice that the table is exactly the same as what I used for the last post on sortable rows with the only difference being the removal of the controls.
The most important part of the table is that I explicitly declare the thead and tbody tags. This makes moving the table rows around easier as they can be accessed through the tbody parent tag.
Coding in Drag
Initially, when deciding to make the table rows draggable (and of course droppable) I was toying with the idea of using the jQuery UI Library but came to the conclusion that it was too much for the task at hand.
To start with, I needed a simple way to keep track of the mouse cursor’s position on the page. jQuery provides an easy way to get fetch this using:
//Capture the mouse x and y positions (only Y is needed for this task but there's no harm in getting // both axis. // Declare global variables so they can be accessed anywhere. // The lastX and lastY variables will be used to keep track of which direction the mouse is heading // when moving the TR elements var mouseX, mouseY, lastX, lastY = 0; // This function captures the x and y positions anytime the mouse moves in the document. $().mousemove(function(e) { mouseX = e.pageX; mouseY = e.pageY; }); |
This simple bit of JavaScript gives access to the mouse X and Y positions anywhere in the script.
The following is the code that allows a TR element to dragged and dropped above or below its siblings in the table:
//IE Doesn't stop selecting text when mousedown returns false we need to check // That onselectstart exists and return false if it does -- we won't check if the browser is IE // As thy may very well change this at some point var need_select_workaround = typeof $(document).attr('onselectstart') != 'undefined'; // The first order of business is to bind a function to the mousedown event // on all TR elements inside the tbody. I am using the jQuery live() function because my content is loaded through // ajax. simply use mousedown() if you do not need to load this on dynamic functions $('table tbody tr').live('mousedown', function (e) { // Store the current location Y axis position of the mouse at the time the // mouse button was pushed down. This will determine which direction to move the table row lastY = mouseY; // store $(this) tr element in a variable to allow faster access in the functions soon to be declared var tr = $(this); // This is just for flashiness. It fades the TR element out to an opacity of 0.2 while it is being moved. tr.fadeTo('fast', 0.2); // jQuery has a fantastic function called mouseenter() which fires when the mouse enters // This code fires a function each time the mouse enters over any TR inside the tbody -- except $(this) one $('tr', tr.parent() ).not(this).mouseenter(function(){ // Check mouse coordinates to see whether to pop this before or after // If mouseY has decreased, we are moving UP the page and insert tr before $(this) tr where // $(this) is the tr that is being hovered over. If mouseY has decreased, we insert after if (mouseY > lastY) { $(this).after(tr); } else { $(this).before(tr); } // Store the current location of the mouse for next time a mouseenter event triggers lastY = mouseY; }); // Now, bind a function that runs on the very next mouseup event that occurs on the page // This checks for a mouse up *anywhere*, not just on table rows so that the function runs even // if the mouse is dragged outside of the table. $('body').mouseup(function () { //Fade the TR element back to full opacity tr.fadeTo('fast', 1); // Remove the mouseenter events from the tbody so that the TR element stops being moved $('tr', tr.parent()).unbind('mouseenter'); // Remove this mouseup function until next time $('body').unbind('mouseup'); // Make text selectable for IE again with // The workaround for IE based browsers if (need_select_workaround) $(document).unbind('selectstart'); reorder(); // This function just renumbers the position and adjusts the zebra striping, not required at all }); // This part if important. Preventing the default action and returning false will // Stop any text in the table from being highlighted (this can cause problems when dragging elements) e.preventDefault(); // The workaround for IE based browers if (need_select_workaround) $(document).bind('selectstart', function () { return false; }); return false; }).css('cursor', 'move'); function reorder () { var position = 1; $('table tbody tr').each(function () { // Change the text of the first TD element inside this TR $('td:first', $(this)).text(position); //Now remove current row class and add the correct one $(this).removeClass('row1 row2').addClass( position % 2 ? 'row1' : 'row2'); position += 1; }); } |
My comments are verbose, so it may make the script look large but don’t be mislead, it is really a very simple script. I now use this script to display the results from a database table and update hidden form fields when a user moves table rows. This can then be submitted and saved to the database.
EDIT:
An extra note on IE
Thanks to Infocyde and Ryan for drawing my attention to the fact that IE still highlights text when dragging the table rows. Internet Explorer doesn’t stop selecting just because the mousedown event returns false.
To stop IE from selecting text, it is required to override the onselectstart function, and make that return false. In my code, I have added a single line inside the jQuery ready() function that flags whether the current user requires this minor workaround.
//IE Doesn't stop selecting text when mousedown returns false we need to check // That onselectstart exists and return false if it does -- we won't check if the browser is IE // As thy may very well change this at some point var need_select_workaround = typeof $(document).attr('onselectstart') != 'undefined'; |
Now that this function has been declared, it’s a simple matter checking whether to bind the selectstart function when the mouse goes down and up. I have updated the code above.
What’s that? A demo?
Not a problem at all. Here you go:
| Position | Name | Colour | An extra column |
|---|---|---|---|
| 1 | Geraldo | Pink | Extra content |
| 2 | Lorenzo | Green | Extra content |
| 3 | Flamingo | Blue | Extra content |
| 4 | Denya | Red | Extra content |
| 5 | Binco | Pink | Extra content |
| 6 | Janya | Yellow | Extra content |
Tags: JavaScript, jQuery, ui

June 24th, 2009 at 3:51 pm
Pretty good post. I just came by your blog and wanted to say
that I have really enjoyed reading your posts. Any way
I’ll be subscribing to your feed and I hope you post again soon!
June 25th, 2009 at 11:31 am
Thanks Maria!
I do always have the *intention* to post often but it isn’t all about good intentions though.
I posted twice within three days so it could be a sign….
June 26th, 2009 at 7:47 pm
Very very nice! Just what I was looking for! I was coding for hours and still nothing close to yours! Thanks!
June 26th, 2009 at 9:14 pm
Hey Ron! Glad it helped you:D Happy coding!
June 27th, 2009 at 3:01 am
Doesn’t seem to work in my IE8….works in FireFox 3.0.8 though
June 27th, 2009 at 3:04 am
Clarification…it works in IE8 but there is some odd selected text issue…the drag highlights the rows its dragging over.
June 27th, 2009 at 3:08 am
Thanks for posting, very useful code. One minor issue, with IE 8 (suprise) when you select and item and start dragging, text highlights as you drag. If I come up with a fix for that I’ll email ya. Thanks again for sharing your code.
June 27th, 2009 at 5:42 am
Hi Ryan and infocyde. Thanks for the heads-up.
I have updated the post and the IE problem should no longer exist.
July 2nd, 2009 at 5:40 am
Great Work! Gotta Loooooove jQuery!
July 23rd, 2009 at 12:15 pm
Great stuff indeed!
Which technical approach would you recommend if we were to turn this into a form to let the user submit it to a database?
August 4th, 2009 at 6:23 pm
This is really lovely script!
jQuery rocks!!
August 6th, 2009 at 12:30 am
Is it possible to put the demo on a separate page so that there is no extraneous code? Your code boxes, to me, seem fragmented so I’m having a difficult time figuring out how this is to be implemented.
August 23rd, 2009 at 4:49 pm
Hi Derrick,
Here’s a cut down version of Lukes code.
Drag, drop and sort table rows with jQuery | Luke Dingle . com
PositionNameColourAn extra column
1GeraldoPink Extra content
2LorenzoGreen Extra content
3FlamingoBlue Extra content
4DenyaRed Extra content
5BincoPink Extra content
6JanyaYellow Extra content
Notice – The two links: the first to the jquery scripts and the second to the scripts shown in the examples above.
Great code Luke. Thanks.
August 24th, 2009 at 11:31 pm
@Fred, I’m not sure if you put some code snippets in there but Wordpress has taken it out
You can try escaping it first?
September 4th, 2009 at 1:26 am
Thanks a lot for this! I’ve just spent the best part of an hour trying to get the jQuery UI version to work properly in IE7/8, but then I found your way looks and functions much better!
September 13th, 2009 at 1:51 am
It’s a good tutorial, thanks. But i have a problem, the table sort is ok but if in my i have an … i can’t have the focus on this input… on a select it’s ok. Can you help me please =)
September 17th, 2009 at 7:40 am
Unfortunately, this does not work if the table cells contain input elements. You can no longer input text.
September 17th, 2009 at 7:49 pm
Thanks for this!
It’s a big plus to my new project.
September 21st, 2009 at 2:30 pm
Excellent job. Can I use it in my website? any license?
Thanks
October 7th, 2009 at 8:37 pm
Nice one.
As a JQuery begginer I noticed that every element manipulated by JQuery changes its FONT appearance to a thinner and paled one. This effect happens here too.
Any idea?
October 17th, 2009 at 9:51 pm
For those of you looking for a solution to the problem of input boxes no longer being usable after the live events are fired for this.
The method I used is to create a wrapper function to go around his code that is triggered with an onmouseover event. I used a simple graphic (a drag / move icon) with an onmousedown place near the input box to indicate the content is sortable etc. So until that icon is clicked on, his code doesn’t fire.
Then I use onmouseup to fire a function with (’*').die(); to kill all live events. You could be more specific to only target live events related to this code.
Anyway, the key to the problem is to use die to take out the live event binding, and then make sure you re-trigger his code again when needed.
October 20th, 2009 at 9:25 pm
Awesome. Thanks Jonathan. I have been a bit slack responding to some comments and your response is perfect for the problem with inputs.
October 23rd, 2009 at 1:36 am
Hi, Very useful functionality.
Have a question:
In a 5 row table, I want the drag and drop for the 3 rows only. How this can be done?
Cheers,
Ranga
November 22nd, 2009 at 10:13 am
Hah, neat! thank you
December 2nd, 2009 at 12:01 pm
Rakuli, try to add border to your table. In this case, when we switch drag direction, we loose first switch. I made some changes to your script, and format it as jQuery plugin. Imho, it’s unnecessary to remember mouse position, we can just check rowIndex.
You can see example here: http://e1f.ho.ua/js/tablesort.js
January 4th, 2010 at 2:40 am
This works perfectly!
I have a question though. How can you drag and drop the table row to a another table?
January 4th, 2010 at 9:18 pm
The simpler way will be to use ui-sortable of jquery and set the option(item:’tr’)
e.g
$(function() {
$(”#formName\\:selector”).sortable({
revert: true ,items: ‘tr’,});
});
PositionNameColourAn extra column
1GeraldoPink Extra content
2LorenzoGreen Extra content
3FlamingoBlue Extra content
4DenyaRed Extra content
January 13th, 2010 at 5:45 am
This is really amazing. I am a beginner and found this really interesting. Thanks a lot for posting this. Cheers!!!
January 16th, 2010 at 1:22 am
Hi
Very nice script
but one problem i am facing if i am dragging rows then the vertical scroll should work automatically after reaching the div ends but its not working
can anybody please help me on this
thanks
irfan
February 9th, 2010 at 1:31 am
Hi, I’m liking this, it’d certainly a little more elegant than using jquery UI. I did run into the same problem that Jonathan did with input boxes, but think I have a slightly more elegant solution to the problem. All you need to do is to call stopPropagation() on the mousedown events of any input elements in the table.
IE:
$(document).ready(function() {
$(’table tbody tr input’).mousedown(function (e) {
e.stopPropagation();
});
});
That way you can still drag the table from any area except an input box, and input boxes get focus properly. I’ve not tested if the same problem appears with textareas or select boxes, but I’d imagine that if it did then replacing input with, or amending to input with, select, textarea, or any other problem element’s tag in the above code would fix them.
February 26th, 2010 at 7:07 pm
Working on a project and playing around with the idea of a sortable table… As of now I have it sorting every table row within the specified table. However, I have a checkbox in a cell that – if clicked – that the information in that row gets included for database entry….
Pseudo: If the checkbox isn’t clicked, the information from that row should be overlooked, not validated, not put into the database – and therefore not relevant to the sort…. I’ve read through the code a few times, and I’m not sure where to throw the IF statement. Or maybe I should be calling something from your functions in the click function of my checkbox instead…
Ugh – I think I’ve gone cross-eyed. I’m a newbie (to jQuery anyway) and could just use a kick in the right direction….
Before my self-absorbed being forgets – THANK YOU for the verbose commenting! I wish more tutorials did that.