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 from GitHub (current version 0.6).
  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>
</table>

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
    $("#table-1").tableDnD();
});
</script>

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:

onDragStyle
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).
onDropStyle
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.
onDragClass
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.
onDrop
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
table.tBodies[0].rows.
onDragStart
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.
scrollAmount
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)
	$("#table-1").tableDnD();

	// 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
	$("#table-2").tableDnD({
	    onDragClass: "myDragClass",
	    onDrop: function(table, row) {
            var rows = table.tBodies[0].rows;
            var debugStr = "Row dropped was "+row.id+". New order: ";
            for (var i=0; i<rows.length; i++) {
                debugStr += rows[i].id+" ";
            }
	        $(#debugArea).html(debugStr);
	    },
		onDragStart: function(table, row) {
			$(#debugArea).html("Started dragging row "+row.id);
		}
	});
});
 
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).

    $('#table-3').tableDnD({
        onDrop: function(table, row) {
            alert($.tableDnD.serialize());
        }
    });

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.

    $('#table-5').tableDnD({
        onDrop: function(table, row) {
            alert($('#table-5').tableDnDSerialize());
        },
        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() {
          $(this.cells[0]).addClass('showDragHandle');
    }, function() {
          $(this.cells[0]).removeClass('showDragHandle');
    });

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

Version History

0.2 2008-02-20 First public release
0.3 2008-02-27 Added onDragStart option
Made the scroll amount configurable (default is 5 as before)
0.4 2008-03-28 Fixed the scrollAmount so that if you set this to zero then it switches off this functionality
Fixed the auto-scrolling in IE6 thanks to Phil
Changed the NoDrop attribute to the class “nodrop” (so any row with this class won’t allow dropping)
Changed the NoDrag attribute to the class “nodrag” (so any row with this class can’t be dragged)
Added support for multiple TBODYs–though it’s still not perfect
Added onAllowDrop to allow the developer to customise this behaviour
Added a serialize() method to return the order of the rows in a form suitable for POSTing back to the server
0.5 2008-07-11 Now supports having a column as a drag handle (specify a class for the dragHandle option when configuring).

Improved the serialize method to use a default (but also settable in the options) regular expression for generating the serialized string. The default is /[^\-]*$/ which will remove everything before a final hyphen, so item-s1 becomes s1.

Added $(‘…’).tableDnDUpdate() to cause the table to update its rows so the drag and drop functionality works if, for example, you’ve added a row.

Added $(‘…’).tableDnDSerialize() which allows you to serialize a table from any javascript code.

Removed remaining $ and replaced with jQuery so that it should work with Prototype and Scriptaculous

Tags: , , ,

763 Responses to “Table Drag and Drop JQuery plugin”

  1. Julio says:

    I can update MYSQL DB :S

    Any GooD Example … ?

  2. Julio says:

    sorry, i say “I CAN´T”

  3. Matt Huggins says:

    Just offering a heads up to anyone trying to use UUID’s as TD ID values. Make sure you set the serializeRegexp value when calling tableDnD, as per the following:

    $(‘#table’).tableDnD({
    serializeRegexp: /^.*$/
    });

  4. Aaron says:

    Thanks for this, it’s a pretty great plugin. I made one tiny addition which I thought you should incorporate back into the release.

    I have a table where every other row is dark. While dragging the rows, I need to update the classes on the fly otherwise the table looks broken because there might be two dark rows together.

    I added an “onDrag” variable to the config right under onDragStart (line 102). Then in the mousemove function on line 279, I added: if (config.onDrag){ config.onDrag(); }

    Hope this helps!

  5. Bill says:

    Thanks this thing is really cool

    I noticed load of people on here looking to drag and drop multiple rows anyone had any luck with that?

    anyone working on it? I have a project where it would meet my spec perfectly if i had this ability.

    otherwise anyone seen a tree with drag and drop reordering that has full html nodes not just text and an image?

    Thanks again

  6. Zapperlot says:

    Is there any possibility to integrate this fine plugin into TinyMCE ? This would be great for me…

  7. Bas says:

    Hi, nice plugin.

    Just wanted to say that the function onDragStart(table, row) actually returns the table cell instead of the table row for me. The documentation above says it should return the row.

    Also, you say that the plugin generates a javascript error when you drag beyond the bounds of a tbody. For me, not being able to drag outside of a tbody turned out to be a feature, very useful to constrain the range. The error message can easily be suppressed by enclosing line 273 and line 275 in try catch blocks. This won’t break the functionality at all.

  8. krits says:

    Hi I am trying to drag and drop with dynamically added rows.. but does not work. The static rows can be dragged but the newly added row is not draggable. Means cannot pick the new row and move.. where as the static rows can be moved even in place of the new row.
    Any suggestion.
    Actually I am creating a table dynamically and wants all the rows to be draggable before saving.

  9. Mike says:

    Does this plugin support nested lists? Or sub-lists as someone else called it.

  10. NomikOS says:

    Regarding onAllowDrop implementation.

    I need parse both parameters in the function passed by onAllowDrop

    So (Pau talk about it above) I changed:

    (line 269 –not line 301– @ version 0.5)

    var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
    by:
    var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj[0], y);

    In this way I can do something like:

    function myAllowDrop(draggedRow, myrow) {
    var mycel = draggedRow.getElementsByTagName(“td”)[0];
    if (mycel.innerHTML != “ ”)
    {
    var mycel = myrow.getElementsByTagName(“td”)[0];
    if (mycel.innerHTML != “ ”)
    return true;
    }
    }

    Thanks Denis for your work…

  11. Linz Jose says:

    Hi,

    I need to implement a drag and drop functionality in web. The situation is, I need to drag content from a popup window and drop it into the parent window. Is that really possible? Please let me know.

    Thanks in Advance

  12. Avaterera says:

    Hi. Just wanted to ask about progress implementing dragging between tables…

  13. Nilesh J. Macwan says:

    Can any one help me with this plug ins

    http://www.isocra.com/2008/02/table-drag-and-drop-jquery-plugin/

    In this plug ins user can drag and drop only one row, my need is to drag and drop Multiple row.

    Thanks
    Nilesh J. Macwan

  14. radyo says:

    Thank you! Very good script!

  15. Seppo Hovi says:

    The (X)HTML-standard indicates that the ID-attribute cannot start with a number which seems to be the key requirement for this plugin to work. Please tell me I’m wrong?

  16. Jon says:

    It doesn’t have to start with a number. I think some of the examples used above do start with a number, but others do not. For example, table5-row-4 is the format for one set of ids in the examples above.

    It is nice to have some kind of key value system for you ids, so that you can relate back to the appropriate database row for updates. You just need to parse out text aspects of the id on the backend.

  17. DerekP says:

    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!

  18. irfan says:

    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

    thanks
    irfan

  19. R says:

    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.

  20. ValsiS says:

    very good

    but i want to drag and drop / and then to vote … i can do this ?

    i want this for an top 20 music :D

    you can help me pls ?

  21. Chris says:

    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,
    Chris

  22. Jon says:

    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;

  23. [...] Table drag and drop jQuery plugin – Link. [...]

  24. Rocky says:

    Can anyone suggest me how to drag multiple rows?

  25. Rocky says:

    Hi Jon Can you please provide me the script you used for dragging and dropping multiple rows

  26. Jon says:

    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:

    row-one

    row-two

    row-three

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

  27. Jon says:

    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.

  28. Rob says:

    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…

  29. Mark says:

    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?
    Thanks!

  30. Jan says:

    Thank You for this precious litte tool. Helped a lot :)

  31. John says:

    Cool script. Love it.

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

    Example:

    row1
    row2
    row2.1
    row2.2
    row3
    row4
    row4.1
    row4.2
    row5

  32. Rocky says:

    Hi Jon Can u please post the script?
    Appreciate your help.

    Have a Great day

  33. John says:

    Here is a link to my script.
    http://www.878282v6y7k.com/dev/

    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

  34. Jon says:

    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.

  35. 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

  36. Bidyasagar says:

    Hi
    I want to drag drop rows between two tables

    Can this possible.

    If yes please send me th code

  37. Chandan says:

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

  38. Dylan Marsh says:

    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 snconners@gmx.co.uk, as I don’t frequent DenisH’s blog often.

    ————–

    Grab TableDnD v0.5 with my mod here: http://pastebin.com/f31a6ee99

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

    jQuery(this).mousedown(function(ev)
    {
    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)
    */
    if(config.onDragStart)
    {
    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;
    });

  39. Jin Wang says:

    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
                    $(".tbl").tableDnD({
                        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 + ";";
                            }
                            $(".test").html(orderStr);
                            $.cookie("order_"+ $(".plabel").text(), orderStr, { expires: 30});
                        },
                        containerDiv: $("#viewContent")
                    });
                    $(".test").html($.cookie("order_"+ $(".plabel").text()));
                });
    
  40. Jin Wang says:

    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);
    	}
    
  41. Gatis says:

    It is possible now drag and drop under parent rows?

    Thanks.

  42. Mike Clinton says:

    Great script! Do you have any plan to add drag/drop column instead of row?

  43. Per says:

    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?
    Thanks

  44. Per says:

    Also, I can’t get tableDnDUpdate() working ;/

  45. Landon says:

    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
    				else
    				{
    					var tmpObject = jQuery(jQuery.tableDnD.dragObject).detach();
    					if (movingDown)
    						tmpObject.insertBefore(jQuery(currentRow));
    					else if ( !movingDown)
    						tmpObject.insertAfter(jQuery(currentRow));
    
    				}
                }
    

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

    Regards,
    Landon

  46. [...] 7. Table row drag and drop 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. [...]

Leave a Reply