Follow my updates with RSS

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:

?View Code JAVASCRIPT
//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:

?View Code JAVASCRIPT
 
//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.

?View Code JAVASCRIPT
//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

VN:F [1.4.6_730]
Rating: 4.6/5 (50 votes cast)

Tags: , ,

31 Responses to “Sortable table rows with jQuery – Draggable rows”

  1. Maria chips in with:

    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!

    VA:F [1.4.6_730]
    Rating: 2.9/5 (10 votes cast)
  2. Rakuli chips in with:

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

    VN:F [1.4.6_730]
    Rating: 3.2/5 (5 votes cast)
  3. Ron chips in with:

    Very very nice! Just what I was looking for! I was coding for hours and still nothing close to yours! Thanks! :D

    VA:F [1.4.6_730]
    Rating: 4.2/5 (5 votes cast)
  4. Rakuli chips in with:

    Hey Ron! Glad it helped you:D Happy coding!

    VN:F [1.4.6_730]
    Rating: 3.0/5 (4 votes cast)
  5. Ryan chips in with:

    Doesn’t seem to work in my IE8….works in FireFox 3.0.8 though

    VA:F [1.4.6_730]
    Rating: 5.0/5 (1 vote cast)
  6. Ryan chips in with:

    Clarification…it works in IE8 but there is some odd selected text issue…the drag highlights the rows its dragging over.

    VA:F [1.4.6_730]
    Rating: 3.3/5 (3 votes cast)
  7. infocyde chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 3.0/5 (2 votes cast)
  8. Rakuli chips in with:

    Hi Ryan and infocyde. Thanks for the heads-up.

    I have updated the post and the IE problem should no longer exist.

    VN:F [1.4.6_730]
    Rating: 3.0/5 (2 votes cast)
  9. Andre chips in with:

    Great Work! Gotta Loooooove jQuery!

    VA:F [1.4.6_730]
    Rating: 3.0/5 (2 votes cast)
  10. Marc chips in with:

    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?

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  11. Aizu Ikmal chips in with:

    This is really lovely script!

    jQuery rocks!!

    VA:F [1.4.6_730]
    Rating: 3.0/5 (2 votes cast)
  12. Derrick chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  13. Fred chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  14. Rakuli chips in with:

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

    VN:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  15. Dave Miller chips in with:

    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! :)

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  16. DoOm chips in with:

    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 =)

    VA:F [1.4.6_730]
    Rating: 5.0/5 (1 vote cast)
  17. L Powers chips in with:

    Unfortunately, this does not work if the table cells contain input elements. You can no longer input text.

    VA:F [1.4.6_730]
    Rating: 5.0/5 (1 vote cast)
  18. Randolph chips in with:

    Thanks for this!

    It’s a big plus to my new project.

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  19. gchand chips in with:

    Excellent job. Can I use it in my website? any license?

    Thanks

    VA:F [1.4.6_730]
    Rating: 5.0/5 (2 votes cast)
  20. Joe Frank chips in with:

    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?

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  21. Jonathan chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 5.0/5 (1 vote cast)
  22. Rakuli chips in with:

    Awesome. Thanks Jonathan. I have been a bit slack responding to some comments and your response is perfect for the problem with inputs.

    VN:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  23. Ranganathan K chips in with:

    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

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  24. romet chips in with:

    Hah, neat! thank you

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  25. zantor chips in with:

    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

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  26. rod chips in with:

    This works perfectly!
    I have a question though. How can you drag and drop the table row to a another table?

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  27. himanshu chips in with:

    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

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  28. Ami chips in with:

    This is really amazing. I am a beginner and found this really interesting. Thanks a lot for posting this. Cheers!!!

    VA:F [1.4.6_730]
    Rating: 5.0/5 (1 vote cast)
  29. irfan chips in with:

    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

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  30. Colin chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)
  31. Stacey chips in with:

    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.

    VA:F [1.4.6_730]
    Rating: 0.0/5 (0 votes cast)

Leave a Reply