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

722 Responses to “Table Drag and Drop JQuery plugin”

  1. Ajay pate says:

    Hy I Have found bug in the table drag and drop plugin.
    i fill the div’s Inner Html with xajax function after words in xajax i called addScript() function to add dynamic script.table is sortable and dragable in any ie,mozilla and opera browser.
    but this cant work in the safari 3+. & i found the following error or alert message

    Type Error: Value undefined(result of expression o.find) is not object.

    Please help me asap.
    :-( :-( :-( :-( :-(

  2. Bala says:

    First off, thanks so much for the plugin. It is really useful and easy to use.

    I have a problem when I have a table inside an iframe. When I drag a row and take it to the top, the page will not scroll (even after explicitly setting scrollAmount to a positive value). Scrolling works on the same table if it is not inside an iframe. Has anyone faced this problem? Has anyone figured out a solution for this?

    Thanks in advance.

  3. ADiel says:

    I described how to connect the script on your blog. Thank you for the precious plugin!

  4. DB says:

    Two things I’ve hacked since using in a live environment.

    One, need to have the row take on the drag class upon click, not just once it begins drag.

    jQuery(rows[i]).mousedown(function(ev) {
    	...
            	// want the same drag color on mouse down
            	$(this).addClass('myDragClass');
            	...
    })
    

    Two, there is a bug with auto scroll that needs addressed. If you are at the top of the window (in config.scrollAmount buffer zone) and dragging down, don’t apply autoscroll up. If you are at the bottom of the window (in config.scrollAmount buffer zone) and dragging up, don’t apply autoscroll down. Must change the placement of the autoscroll code to take “movingDown” into account.

    // TODO worry about what happens when there are multiple TBODIES
     if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
    
       	var windowHeight = window.innerHeight ? window.innerHeight
               	: document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
    
             	// If we are dragging up from bottom of window,
              	// don't apply any scrolling please.
              	if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount)
    		window.scrollBy(0, config.scrollAmount); 
    
            	jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
    
     } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
    
           	// If we are dragging down from top of window,
             	// don't apply any scrolling please.
             	if (mousePos.y-yOffset < config.scrollAmount)
             		window.scrollBy(0, -config.scrollAmount);
    
            	jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
    
    }
    
  5. [...] Whit this really cool plugin you can reorder and filter items with a nice shuffling animation. The demo is a must see! Table Drag and Drop JQuery plugin [...]

  6. TomM says:

    Hi all,

    I have read this post and every single reply a dozen times. I’m trying to reorder an SQL table via PHP. I’ve read some responses that say “just do an ajax call” or “put the values in a hidden field”.

    My problem is I’m extremely new with javascript. I don’t know how to do those things. I’m trying to do something like the 3rd example but it seems incomplete. I checked the “PHP Source” link but PHP is not my issue, it’s getting the data TO PHP that I don’t know how to execute. PLEASE, PLEASE, PLEASE help me!

  7. DenisH says:

    Hi @TomM,

    You’re right, there’s a vital line missing from the code display for example 3. Sorry, in all this time I hadn’t noticed. It’s only one line, and just uses the standard jQuery Ajax load call. Here it is, highlighted:

     $('#table-3').tableDnD({
        onDrop: function(table, row) {
            alert("Result of $.tableDnD.serialise() is "+$.tableDnD.serialize());
            $('#AjaxResult').load("/articles/ajaxTest.php?"+$.tableDnD.serialize());
        }
    });
    

    Basically, you specify a part of the current page that you want to replace with the results of the Ajax call (in this case the element whose id is “AjaxResult”), and then you call load(url). To attach the information for PHP or whatever, you call $.tableDnD.serialise() and it produces the parameters and values in the format described above.

    Hope this helps?

  8. TomM says:

    Hey Denis,

    THANK YOU! That was exactly what I needed to know and I very much appreciate such a speedy response. The plugin is so great, but yeah, my knowledge of JS/JQuery is still sort of in its infancy, so I was just missing what the call to push it to PHP was. Now that I have the data pushing to something I know how to use I’ve got it working just how I need it to. A thousand thank yous!

  9. arpit says:

    hello sir, can you please tell how i canapply inline edit and drag and drop together in jquery. My inline edit was working, now i have added drag and drop which has stopped edit to work. please guide through! thanks to all experts over here!!!!!!!!

  10. Gigi says:

    Hi everyone.
    I need to know how can I get the new order of the rows so I can insert the new rows in MySQL.

    Thanks.

  11. Tim says:

    Thanks so much. I know I’ll be using this all the time now. I can’t beleive I never knew about this and it’s so simple!

  12. Alessandro says:

    Really weird thing going on here.. basically on dragstart i block off certain rows as nodrop.. then on drop i get all the rows without no drop class..

    everything is fine, until i add the piece of code with the ajax. The variable it passes is the orginal order not the new order.. however without the ajax code the variable is correct??

    onDrop: function(table, row) {
    			var debugStr = "";
    			var rows = table.tBodies[0].rows;
    		//	console.log(row.id);
    			for (var i=0; i<rows.length; i++) {
    
    			if($(rows[i]).hasClass("nodrop") == 0)
    			{//console.log(rows[i].id);
    			debugStr += rows[i].id+",";}}
    			console.log(debugStr);
    			debugStr1 = new Array();
    			debugStr1 = debugStr.split(",").clean("");
    				console.log(debugStr1);
    
    			$("##table-1 tr").removeClass('nodrop inactive');
    		/*	$.ajax({
    					  url: "index.cfm?action=#arguments.action#&shuffle=true&ajax=true&id="+debugStr1,
    
    							success: function(data) {
    								$('##promo_rtp').html(data);
    
    								}
    				  });/**/
    		    }
    
  13. nufra says:

    HI, great job with your plugin…may i make a question? There is a way to kill the jquery object, cause i will to activate and deactivate the drag&drop on my table.

    Thanks in advance.

  14. Michael says:

    I looked here a while ago for a solution that would let you “group” rows using ONE table and ONE tbody. I didn’t see anything (and I’m too lazy to search this massive thread now), so I wrote my own and figured I’d share with the community.

    Demo: http://www.michaelsoutherton.com/sandbox/tablerowdrag/
    Initialization: http://www.michaelsoutherton.com/sandbox/tablerowdrag/jquery.tablerowdrag_groups.setup.js

    It requires a pretty specific setup to your table, so it probably won’t fit everyone’s needs. And I’m a jQuery amateur at best, so I’m sure there’s a million better ways to write this. Hope it helps though.

  15. TomM says:

    Hi Gigi,

    I just did this, so I’ll show you how I accomplished it. Someone can probably come along and refactor my code to be even more efficient as well. This particular bit of code reorders my product categories:

    First, call the ajax insert to a div (here called #ajaxresult) just below my table –

    onDrop: function(table, row) {
    $(‘#ajaxresult’).load(“reorder.php?”+$.tableDnD.serialize());
    }

    Then, the php script to reorder. This will show a “reorder” and “cancel” button below my table when a row is dropped. If submitted, it will reorder the rows in the SQL database -

    <?php // If reorder is submitted
    if (isset($_POST['update'])) {
    $size = $_POST['how_many'];
    for ($k=1; $k<=$size; $k++) {
    $old = $_POST[$k];
    $query = "SELECT cat_id FROM categories WHERE position='$old'";
    $result = mysql_query($query, $connection);
    confirm_query($result);
    $catids[$k] = mysql_fetch_row($result);
    }
    $i = 1;
    while ($i

    <?php // Runs on reorder drag
    $result = $_REQUEST["cat_table"];
    $count = count($result);
    $how_many = $count – 1;
    for ($j=1; $j<=$how_many; $j++) {
    $old = $result[$j];
    $new_value = $j;
    echo "”;
    }
    echo “”;
    ?>

    Hope that helps!

  16. TomM says:

    Hmmm…sorry, I don’t know how to get that nice little code box in my comments. The script posted is incomplete. If someone can let me know how to post code correctly, I’ll repost. Don’t want to blow up the comments attempting it!

  17. Steven says:

    I got onDragClass to work, but I cannot seem to change the class or style of the dropped row. I want it to stay changed after dropping and ever use of onDropStyle that I could think of does not work. Can you provide an example for that?

  18. Steven says:

    I managed to get the dropped box to hold a new style by doing this.

    onDrop:function(table,row) {
    $(“#”+row.id).addClass(“movedRow”);
    }

    I was surprised that I could not (or did not know how) to use the value of row to change the row’s class and I tried various combinations of $(row.id). Only this morning it dawned on me that perhaps I needed to add “#”+. It still seems to me this is a bit of a round-about way to get it done. Is there something more straight forward? If the element is contained in row, shouldn’t I be able to manipulate it more directly?

  19. Chris says:

    Steven, thanks for your example. It did exactly what I needed it to do. It’s great that this thread is over two years old and still being contributed to.

  20. [...] Table 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. [...]

  21. Tahir Hassan says:

    Steven – Because the row parameter is already an element, you can pass this straight into $:

    onDrop: function(table,row) {
    $(row).addClass(“movedRow”);
    }

  22. eric says:

    hi,
    I have a table that I use the drag and drop for ordering. I have a button that allows the user to add a new row… however, the new row does not work with the drag and drop… any ideas?

  23. Jason says:

    Love this script.
    Just curious version 1.0 will ever make it out. Anyone test this script on IE8?

  24. [...] Table 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. [...]

  25. raj kumar says:

    Hi,
    Thanks for the script, I have a question,
    I am looping over TR and creating a table with items I added this drag and drop feature, my question when I drag a TR first TD in TR should Stay still and rest should move? any suggestion.

    Thanks Again for this script.

  26. Raymond says:

    hi,
    i want to change the table row td color after drag and drop the row. i modify the onDrop function and add a class. It seems that the row color will change after i click the row even i have not change its position. I only want to change the row color after dragging to other position. can anyone tell me how i can do that?

  27. Raymond says:

    onDrop:function(table,row) {
    $(“#”+row.id).addClass(“myDropClass”);
    },

  28. webders says:

    wuu nice plugin,thanks

  29. Smita says:

    Hello all.
    First of all, this is a great plugin. It works great for me. Please forgive me if I am asking a totally strange/dumb question. For my table, I implemented the dragStart and onDrop events. Whenever I drag and drop a row, the sequence of events is something like dragStart -> onDrop -> dragStart. Can someone please tell me on how to stop dragStart being called the 2nd time? Thanks a lot.

  30. It’s very useful plugin, thanks a lot!

  31. HI
    loving the script!
    I have a table each row has an input box for the page order, does anybody know how to update the input boxes with the new row position?

  32. QL says:

    I have a table on the left. And some categories on the right. The table rows show the files present in each category when a category is selected. A file can be dragged and drop into another category. I have a problem. Whenever I drag a file and drop it into a category, it doesnt go into that category instead it goes into the category directly above it. Anybody has any suggestions on how to solve this problem?

  33. Philipp says:

    Hello, I really appreciate your script, especially the way you wrote the code. It is really easy to modify.
    Here a tiny enhancement of my own:
    Problem setting: I have multiple tBodies and want to move between them.
    The tBodies can happen to have no element and maybe several headings that should not be movable.

    Now the problem is, that if you declare the headings as nondrag & nondrop you cannot fill an empty tbody.
    You could also declare the lowest heading “nondrag”able only, but then, the code allows you to put elements between the headings.
    Hope it got more or less clear, what the problem was.. here comes the solution: (most parts taken from http://gist.github.com/14807)

    var nodrag = jQuery(currentRow).hasClass("nodrag");
    if ( (movingDown || nodrag ) && jQuery.tableDnD.dragObject != currentRow) {
    	currentRow.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
    }
    else if (! movingDown && jQuery.tableDnD.dragObject != currentRow && !nodrag) {
    	currentRow.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
    }

    It does basically the following: If you want to move the row from the top between a nondrop and a nondrag row, the drop will be made below the nondrag.
    eg
    ..

    heading
    <————————————————- before, it was possible to drop here.
    subheading
    .. content>

    good night

  34. Josh says:

    What about having the table horizontally? I am trying to do something with this plugin where I can rotate the html table any which way, and still have drag and drop functionality. Is this possible?

  35. [...] if they are able to change the order of invoice lines quickly. In my application I used jqGrid with TableDND extension. Here’s how I got it [...]

  36. Hi Denis

    Thanks for the great work on this tool; it’s become very helpful, allowing me to build a really nice, intuitive UI component for our CMS.
    Whilst I was doing this, I stumbled across a problem with nested draggable tables where content was being generated dynamically and found a solution.

    According to the notes above, tableDnDUpdate() should be called after a draggable table has been altered; in my tests, this doesn’t always work and can lead to Javascript errors in line 275 of the code.

    After a lot of faffing about, I found a solution: instead of using tableDnDUpdate(), I use tableDnD() after I’ve altered the table in question. This then seems to sort out problems with this function.

    Cheers!

  37. bobby says:

    Wow.. nice plugin its hleps me a lot..

  38. Barclay says:

    How can I get the row# or rowID that the row was dragged to? I saw an example by Nathan but couldn’t get it to work.

  39. Snowbird says:

    If you need sorted ids passed by ajax call after a button click when you finish sorting you can use following example

        	$("#table-cat").tableDnD({
        		onDrop: function(table, row) {
        			var order = $.tableDnD.serialize('id');
        			$('.groovybutton').click(function() { $('#result').load("ajax_handler.php?" + order); $('#sort').hide(); });
    			$('#sort').show('slow');
    			}
        	});
    

    Put a button in a DIV block but keep hidden when page load

    ‘);

  40. Cowboy says:

    Could it be that the implementation is not working anymore? Whatever I try I just can’t get the (stock) example up and running. Any help?

  41. Jannes says:

    Hi All,

    I’m also working with oscommerce, i’ve already implemented the catergorie sort order on my own.
    Now i also want to implement it for the products. They are in the same table as the categories.
    Is it posible to ’split’ the function on for example the class ?

    dragHandle: ‘.dragme’ is for the categories.

    I thought about copying this code and changing the classname for the products.
    But that doesn’t work…

    Anyone? Thanks in advance

  42. django says:

    @Barclay

    maybe this helps

    [code]
    $("#myTable").tableDnD({
                onDrop: function(table, row) {
                   droppedRow=$(row).attr('id');
                   prevRow=$('#'+rowDropped).prev().attr('id');
                   nextRow=$('#'+rowDropped).next().attr('id');
                });
    [/code]
    
  43. django says:

    there’s a mistake in my prev posting. replace rowDropped with droppedRow

  44. Dwight says:

    How do you past query variable to php?

Leave a Reply