Table Drag and Drop JQuery plugin

I’ve been using JQuery for a while now and really agree with its tag line that it’s the “The Write Less, Do More, JavaScript Library”. We’ve also got this code for dragging and dropping table rows that has proved very popular, so it seemed natural to combine the two and wrap up the table drag and drop as a JQuery plugin.

Why have another plugin?

Dragging and dropping rows within a table can’t be handled by general purpose drag and drop utilities for a number of reasons, not least because you need to move the whole row, not just the cell that receives the mouse events. Re-parenting the row also requires specific code. Sadly also, effects like fadeIn and fadeOut don’t work well with table rows on all browsers, so we have to go for simpler effects.

What does it do?

This TableDnD plugin allows the user to reorder rows within a table, for example if they represent an ordered list (tasks by priority for example). Individual rows can be marked as non-draggable and/or non-droppable (so other rows can’t be dropped onto them). Rows can have as many cells as necessary and the cells can contain form elements.

How do I use it?

  1. Download Download jQuery (version 1.2 or above), then the TableDnD plugin (current version 0.7).
  2. Reference both scripts in your HTML page in the normal way.
  3. In true jQuery style, the typical way to initialise the tabes is in the $(document).ready function. Use a selector to select your table and then call tableDnD(). You can optionally specify a set of properties (described below).
1 One some text
2 Two some text
3 Three some text
4 Four some text
5 Five some text
6 Six some text

The HTML for the table is very straight forward (no Javascript, pure HTML):

<table id="table-1" cellspacing="0" cellpadding="2">
    <tr id="1"><td>1</td><td>One</td><td>some text</td></tr>
    <tr id="2"><td>2</td><td>Two</td><td>some text</td></tr>
    <tr id="3"><td>3</td><td>Three</td><td>some text</td></tr>
    <tr id="4"><td>4</td><td>Four</td><td>some text</td></tr>
    <tr id="5"><td>5</td><td>Five</td><td>some text</td></tr>
    <tr id="6"><td>6</td><td>Six</td><td>some text</td></tr>

To add in the “draggability” all we need to do is add a line to the $(document).ready(...) function
as follows:

<script type="text/javascript">
$(document).ready(function() {
    // Initialise the table

In the example above we’re not setting any parameters at all so we get the default settings. There are a number of parameters you can set in order to control the look and feel of the table and also to add custom behaviour on drag or on drop. The parameters are specified as a map in the usual way and are described below:

This is the style that is assigned to the row during drag. There are limitations to the styles that can be associated with a row (such as you can’t assign a border—well you can, but it won’t be displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as a map (as used in the jQuery css(...) function).
This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations to what you can do. Also this replaces the original style, so again consider using onDragClass which is simply added and then removed on drop.
This class is added for the duration of the drag and then removed when the row is dropped. It is more flexible than using onDragStyle since it can be inherited by the row cells and other content. The default is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your stylesheet.
Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table and the row that was dropped. You can work out the new order of the rows by using
Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the table and the row which the user has started to drag.
This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, FF3 beta)

This second table has has an onDrop function applied as well as an onDragClass. The javascript to set this up is as follows:

$(document).ready(function() {

	// Initialise the first table (as before)

	// Make a nice striped effect on the table
	$("#table-2 tr:even').addClass('alt')");

	// Initialise the second table specifying a dragClass and an onDrop function that will display an alert
	    onDragClass: "myDragClass",
	    onDrop: function(table, row) {
            var rows = table.tBodies[0].rows;
            var debugStr = "Row dropped was "". New order: ";
            for (var i=0; i<rows.length; i++) {
                debugStr += rows[i].id+" ";
		onDragStart: function(table, row) {
			$(#debugArea).html("Started dragging row ";
1 One
2 Two
3 Three
4 Four
5 Five
6 Six
7 Seven
8 Eight
9 Nine
10 Ten
11 Eleven
12 Twelve
13 Thirteen
14 Fourteen

What to do afterwards?

Generally once the user has dropped a row, you need to inform the server of the new order. To do this, we’ve added a method called serialise(). It takes no parameters but knows the current table from the context. The method returns a string of the form tableId[]=rowId1&tableId[]=rowId2&tableId[]=rowId3...
You can then use this as part of an Ajax load.

This third table demonstrates calling the serialise function inside onDrop (as shown below). It also demonstrates the “nodrop” class on row 3 and “nodrag” class on row 5, so you can’t pick up row 5 and
you can’t drop any row on row 3 (but you can drag it).

        onDrop: function(table, row) {

Ajax result

Drag and drop in this table to test out serialise and using JQuery.load()

1 One
2 Two
3 Three (Can’t drop on this row)
4 Four
5 Five (Can’t drag this row)
6 Six

This table has multiple TBODYs. The functionality isn’t quite working properly. You can only drag the rows inside their own TBODY, you can’t drag them outside it. Now this might or might not be what you want, but unfortunately if you then drop a row outside its TBODY you get a Javascript error because inserting after a sibling doesn’t work. This will be fixed in the next version. The header rows all have the classes “nodrop” and “nodrag” so that they can’t be dragged or dropped on.

H1 H2 H3
4.1 One
4.2 Two
4.3 Three
4.4 Four
4.5 Five
4.6 Six
H1 H2 H3
5.1 One
5.2 Two
5.3 Three
5.4 Four
5.5 Five
5.6 Six
H1 H2 H3
6.1 One
6.2 Two
6.3 Three
6.4 Four
6.5 Five
6.6 Six

The following table demonstrates the use of the default regular expression. The rows have IDs of the form table5-row-1, table5-row-2, etc., but the regular expression is /[^-]*$/ (this is the same as used in the NestedSortable plugin for consistency). This removes everything before and including the last hyphen, so the serialised string just has 1, 2, 3 etc. You can replace the regular expression by setting the serializeRegexp option, you can also just set it to null to stop this behaviour.

        onDrop: function(table, row) {
        dragHandle: ".dragHandle"
1 One some text
2 Two some text
3 Three some text
4 Four some text
5 Five some text
6 Six some text

In fact you will notice that I have also set the dragHandle on this table. This has two effects: firstly only the cell with the drag handle class is draggable and secondly it doesn’t automatically add the cursor: move style to the row (or the drag handle cell), so you are responsible for setting up the style as you see fit.

Here I’ve actually added an extra effect which adds a background image to the first cell in the row whenever you enter it using the jQuery hover function as follows:

    $("#table-5 tr").hover(function() {
    }, function() {

This provides a better visualisation of what you can do to the row and where you need to go to drag it (I hope).

938 thoughts on “Table Drag and Drop JQuery plugin”

  1. Nino printed some work back in 08 on how to use with scrollable div. Seems (prob due to cut-n-paste issues) to be problematic. does anyone have this effort functional? New (sadly) to all this javascript/jquery so I’m weak and learning but need to use something like this
    Any help appreciated – Thanks!

  2. Hi
    very nice script

    can anybody help on the following senerio

    i am having one html table inside the div and i am trying to drag the rows its working for me but the vertical div scroll is not working if the selected ro dragged at the end of the div

    please give me any solution


  3. Good stuff.

    To drag only when using the left button, insert if (ev.which != 1) { return false; }
    at the beginnig of the two .mousedown(function(ev) { (...) handlers.
    the which property is provided by jQuery, so no need to worry about compat.
    Also, since “dragObject” is not assigned, there is no need to modify the *move and *up events.

  4. Hi guys,

    Thanks for this great script !
    I need to implement a sortable list (with drag n’ drop). It works fine.. but I need to use pagination to navigate through the list.
    Do someone have an idea on how to implement this drag n’drop script with a pagination ?

    Many thanks,

  5. I know that people have been asking for a while how to drag table bodies, instead of just individual rows. I needed this functionality for a project, so I spent some time figuring out how to implement the solution.

    It turns out that this requires relatively minor changes to the script. By changing the two lines below, you can drag table bodies just as you drag rows. If you use this solution, you will have to enclose even individual rows in tags. However, the nice part is that you can drag multiple rows as if they were a single row.

    line 130: jQuery.tableDnD.dragObject = this.parentNode.parentNode;

    line 285: var rows = jQuery.tableDnD.currentTable.tBodies;

  6. Hi Rocky,
    The solution I posted should work for you, if you are using the version of tablednd from this site.

    You will need to enclose individual rows in tags, in addition to enclosing multiple sets of rows.

    So for example:




    Let me know if it is still not working, and I can paste the complete script. However, I only made the two changes above.

  7. I think I see the problem….my code only works if you use a drag handle. I will have to make another tweak in another place if you aren’t using a draghandle. Let me know if this is what you need.

  8. Excellent script. Was initially confused that there are two versions of the script – one for use with jQuery, and one without. This explained the “tableDnD is not a function” error message I was getting when trying to use the non-jQuery version…

  9. Jon – Thanks for your tweaks to enable the selction of multiple rows using the Drag Handle. You mentioned that it could work without the use of the Drag Handle. Would you be so kind as to show us to make it work without the use of the drag handle?

  10. Cool script. Love it.

    I want to be able to use your script to nest rows under their parent rows. Is this possible?



  11. Here is a link to my script.

    I would like to be able to nest rows under their parent rows. Something like this.

    [drag] Sports 2 Yes
    —[drag] Golf 2 Yes
    —[drag] Football 2 Yes
    —[drag] Baseball 2 Yes
    [drag] Tech 2 Yes
    —[drag] Mobile 2 Yes

  12. If you change line 142 to:

    var rows = jQuery(“tbody”, table); // get all the rows as a wrapped set

    …that should work to drag tbodies with any td. You will also have to make this change I posted earlier to line 285:

    var rows = jQuery.tableDnD.currentTable.tBodies;

    That should work for dragging tbodies when draghandle is not specified. I haven’t had time to test this thoroughly…so there might be bugs. I have done most of my work with the drag handle functionality enabled.

    Hope that helps. Let me know if there are problems.

  13. Any news on dragging between tables?

    With two tables (left and right) I can implement something like ‘available’ and ‘selected’ by dragging rows from left to right table and sorting the selected items by dragging a row up and down in the richt table

  14. Thanks for nice script, but i want the swaping of rows, means if the inicially the arrangement is
    if i drag r4 to r1 then the arrangement will be

  15. Hey Everyone,
    First of all, I’d like to give props to DenisH for the great plug-in! It really saved my a lot of time on a Form Builder project I’ve been working on recently. I am, however, disappointed to see that development on TableDnD seems to have come to a halt, but I know how it is maintaining free software — a thankless job to some degree.

    Anyway, the main reason I’m posting is to share a minor modification I made to your script. After I coded this “hack” (which took only about 20 minutes, thanks to your fantastic API), I realized that it’s not that uncommon a need, and that other developers might be wishing there was a similar feature implemented.

    Basically, I needed to prevent the onDragStart callback from being called just because of a quick, single click on a drag-able row. What I mean is, I didn’t want my onDragStart callback being called when the user was just CLICKING on the row, and NOT trying to actually drag the row around. In the Form Builder script I’m developing, the onDragStart callback causes the inline “Edit Question” form to be hidden, which is required if your dragging a row — but just irritating if you don’t intend to actually drag it. So basically, this little mod just makes the script a little more intuitive (IMO), and helps to determine what the users intent was (to drag, or simply click?).

    To implement this, I modified the makeDraggable method in the plug-in itself. Inside the conditional that checks to see if an onDragStart callback is specified, I added a few lines. First the mouse offset position (Y cord only) is stored when the dragCell is initially clicked. Then, bind a callback to the “mousemove” event on the dragCell element. When mousemove fires, I simply get the new mouse offset Y cord, then check to see if it’s changed (increased or decreased) by at least 2 pixels (you could easily change it to however many pixels). If it has, the onDragStart callback is called, otherwise it’s not. In either case, I DO clean-up by unbinding the mousemove callback.

    I hope someone may find this snippet helpful. I thought it was a useful little enhancement. Please do note, the code I’ve included ONLY works if your using a dragCell (not the entire row). However, one could easily take my code snippet and move it into the else statement a few lines below which is for those not using a dragCell. I just didn’t need that.

    If anyone needs some help with this, feel free to drop me a line at, as I don’t frequent DenisH’s blog often.


    Grab TableDnD v0.5 with my mod here:

    Or, here’s just the modified code itself, starting around line 146 of v0.5:

    jQuery.tableDnD.dragObject = this.parentNode;
    jQuery.tableDnD.currentTable = table;
    jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);

    ** Only call onDragStart if
    ** mouse moves > 2px (on Y)
    var startOffset = jQuery.tableDnD.mouseOffset.y;

    jQuery(this).bind(“mousemove”, function(ev)
    var newOffset = jQuery.tableDnD.getMouseOffset(this, ev).y;

    if(newOffset > startOffset + 2 || newOffset < startOffset – 2)
    config.onDragStart(table, this); // Call onDragStart callback

    return false;

  16. i have just used the table DND and saved sort-list in cookies. That means the sor-list can be reloaded on table-sort initing later.
    The sort-function can be inserted in build block of tableDnD main codes.

    $(document).ready(function() {
                    // Initialise the table
                        onDragClass: "myDragClass",
                        onDrop: function(table, row) {
                            var rows = table.tBodies[0].rows;
                            var orderStr = "";
                            for (var i = 0; i < rows.length; i++) {
                                orderStr += rows[i].id + ";";
                            $.cookie("order_"+ $(".plabel").text(), orderStr, { expires: 30});
                        containerDiv: $("#viewContent")
                    $(".test").html($.cookie("order_"+ $(".plabel").text()));
  17. Hallo Nino Dafonte,

    your codes of “TableDnd in Scrolled Div” have a bug, that the scroll can only be scrolled down. Scroll up is not working . The codes shuld be changed like so:

    	var container = config.containerDiv;
    	var yOffset = container.scrollTop();
    	var windowHeight = container.innerHeight();
    	var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;		
    	var upperBorder = container.position().top  + container.innerHeight(); // not error, it should also be added because mouse moves up
    	var bottomBorder = container.position().top + container.innerHeight();		
    	if (mousePos.y > bottomBorder) {
    		container.scrollTop(container.scrollTop() + config.scrollAmount);
    	if (mousePos.y < upperBorder) {
    		container.scrollTop(container.scrollTop() - config.scrollAmount);
  18. I have a problem with this plugin when using dragHandle.
    If you stay with the cursor with mousedown just between 2 td’s the drag image will appear on both. 🙁
    It’s very annoying. :/
    Anyone could help me fix it? I’ve tried alot and I don’t think this plugin will be updated anymore, right?

  19. I was able to get the multi-tbody functionality working with a little effort:

    Modify code near line 270:

    if (currentRow) {
                    // same tbody
    				if (jQuery.tableDnD.dragObject.parentNode == currentRow.parentNode)
    					// make sure we've moved
    					if (jQuery.tableDnD.dragObject != currentRow)
    						if (movingDown)
    							jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
    						else if ( !movingDown)
    							jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
    				// different tbody
    					var tmpObject = jQuery(jQuery.tableDnD.dragObject).detach();
    					if (movingDown)
    					else if ( !movingDown)

    Note: requires jquery 1.4! (the detach() method makes the magic happen…)


Leave a Reply

Your email address will not be published. Required fields are marked *