Wednesday, April 6, 2011

Sort Table Rows By Drag and Drop Using jQuery

jQuery UI allows you to make a list of elements sortable, but if you want to use it to sort table rows you have to do a few workarounds. This is the code I started with.

$(document).ready(function () {

    //make table rows sortable
    $('#myTable tbody').sortable({    
        helper: function (e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            return ui;
        },
        scroll: true,
        stop: function (event, ui) {
            //SAVE YOUR SORT ORDER                    
        }
    }).disableSelection();

});



The helper function (courtesy of The Foliotek Dev Blog) fixes the width of the row which collapses as soon as you remove it from the table.

This should work for most scenarios, but I've found a bug in Firefox where the row will jump up and float above the cursor when there are scrollbars present on the web page. It seems to be a position issue caused by parent elements of the table. I still haven't figured out exactly what the magic combination is that causes this but I can replicate it with HTML5 Boilerplate when using the default style sheet and table below.

I've replicated the bug on jsFiddle here:
http://jsfiddle.net/cdeutsch/2Yxw2/

<div id="container">
<header>

</header>
<div id="main" role="main">
    <div style="position: relative;">
    <table id="myTable">
        <tbody>
        <tr>
            <td>1</td>
            <td>Blah Blah Blah Blah</td>
        </tr>
        <tr>
            <td>2</td>
            <td>2222 22222 22222 2222 22222</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Test Test Test Test Test</td>
        </tr>
        <tr>
            <td>4</td>
            <td>4444 4444 4444 4444 4444</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Hi Hi Hi Hi Hi Hi</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Bye Bye Bye Bye Bye Bye Bye</td>
        </tr>
        </tbody>
    </table>
    </div>
</div>
<footer>

</footer>
</div> <!--! end of #container -->


If this happens use the code below (inspired by this Stackoverflow question with some tweaks) to fix the issue.

Try it in jsFiddle here:
http://jsfiddle.net/cdeutsch/WysJL/

$(document).ready(function () {
    //make table rows sortable
    $('#myTable tbody').sortable({
        start: function (event, ui) {
            //fix firefox position issue when dragging.
            if (navigator.userAgent.toLowerCase().match(/firefox/) && ui.helper !== undefined) {
                ui.helper.css('position', 'absolute').css('margin-top', $(window).scrollTop());
                //wire up event that changes the margin whenever the window scrolls.
                $(window).bind('scroll.sortableplaylist', function () {
                    ui.helper.css('position', 'absolute').css('margin-top', $(window).scrollTop());
                });
            }
        },
        beforeStop: function (event, ui) {
            //undo the firefox fix.
            if (navigator.userAgent.toLowerCase().match(/firefox/) && ui.offset !== undefined) {
                $(window).unbind('scroll.sortableplaylist');
                ui.helper.css('margin-top', 0);
            }
        },
        helper: function (e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            return ui;
        },
        scroll: true,
        stop: function (event, ui) {
            //SAVE YOUR SORT ORDER                    
        }
    }).disableSelection();
});


Hope this helps someone. Took me about 1/2 a day to fix the Firefox issue.