163

I am using jQuery UI sortable to make my table grid sortable. The code seems to work fine but because I am not adding width to tds, when I drag the tr it shrinks the content.

For example; if my table row is 500px when I start dragging, it becomes 300px. I assume that's happening because no width is defined in the grid. That's because I am using two classes for the tds (fix and liquid).

The fix class makes the td equal to the content width and liquid makes the td width 100%. It's my approach for grid table without having to assign width to tds.

Any idea how to make sortable work with my approach?

j0k
  • 22,600
  • 28
  • 79
  • 90

13 Answers13

262

I found the answer here.

I modified it slightly to clone the row, instead of adding widths to the original:

  helper: function(e, tr)
  {
    var $originals = tr.children();
    var $helper = tr.clone();
    $helper.children().each(function(index)
    {
      // Set helper cell sizes to match the original sizes
      $(this).width($originals.eq(index).width());
    });
    return $helper;
  },
Dave James Miller
  • 4,928
  • 3
  • 20
  • 18
  • @Dave James Miller How would you make the placeholder option work? –  Mar 26 '12 at 20:44
  • 5
    Dave, thanks for sharing this, I have made a jsFiddle to show the differences between original, modified, and no fix applied: http://jsfiddle.net/bgrins/tzYbU/. I will also update the original post with your solution. – Brian Grinstead May 30 '12 at 20:01
  • 10
    This works quite nicely, but I think there's still one slight problem; when you are dragging/dropping the row, the rest of the table columns might change width because of the missing row. I guess their widths need to be fixed as well... – Jez Nov 07 '12 at 15:06
  • How could I apply this fix to the draggable event, the helper method works differently. http://stackoverflow.com/questions/15432555/jquery-ui-draggable-how-to-clone-row-same-as-sortable – John Magnolia Mar 15 '13 at 12:41
  • You are missing semicolon after `width())`. Cant edit unfortunately. Otherwise great answer. – Grzegorz Kaczan May 23 '13 at 10:47
  • This sorted the exact same issue i was having. Great answer, i would recommend this solution to anyone – Gerico Oct 02 '13 at 09:41
  • 1
    This saved me so much work. It should be marked as the correct answer. – Andrew Bessa Apr 24 '15 at 20:22
  • @yaroslav's answer worked great for me. `.ui-sortable-width { display: table }` – stevenspiel Apr 28 '15 at 14:32
  • When you use the modified fix, if you have a checked radio button in the row you move, you loose the state... But not with the original fix... http://jsfiddle.net/nw232gaj/ – Pouki Jan 06 '16 at 16:36
  • 7
    I found the width was resulting in padded cells not QUITE being right. I swapped for `$(this).width($originals.eq(index).outerWidth());` – Barry Carlyon Nov 22 '16 at 16:47
  • i think it happens because the bootstrap's `table` class (v4 alpha5) – Sagiv b.g Feb 06 '17 at 12:56
  • ok found out why this solution didnt work for me, i used the `col-sm-*` classes of botstrap. so i removed it and used a different approach to get similar design width `` for example: ` ` – Sagiv b.g Feb 06 '17 at 14:24
  • @Jez - The width (or widths) of the row or cells need to be applied to the placeholder as well. It seems better to copy the widths of the cells from the helper to the placeholder, adjusting for padding and borders as appropriate instead of just applying a width on the row. – Borgboy Feb 21 '17 at 19:43
201

I think it can help:

.ui-sortable-helper {
    display: table;
}
Yaroslav
  • 3,168
  • 1
  • 15
  • 14
  • 16
    How is this not the top answer? One simple line of CSS fixes it for me. – jcrowson Apr 02 '15 at 23:23
  • 8
    I agree! You won't BELIEVE how one simple line of CSS fixes your sortable table--Stackoverflow experts are baffled! – Brade May 22 '15 at 15:04
  • 3
    This is THE answer. Thanks @Yaroslav – Steffi Jun 03 '15 at 15:17
  • 3
    This is NOT a general solution and will result in different look of the dragged row – Tomas M Aug 04 '15 at 10:02
  • In my case, this only resulted in the tr shrinking LESS than it was before, but it is still changing size and doesn't look right. – little_birdie Aug 28 '15 at 21:35
  • @little_birdie: I agree with you exactly. However, the simplicity and (I guess) low footprint of this solution goes a long way to compensate for this deficit, IMHO. – Ifedi Okonkwo Nov 11 '15 at 22:04
  • Worked for me better than the top solution. The top solution had some bugs when dragging outside the parent div. – Max Flex Nov 30 '15 at 07:49
  • 16
    This solution is very good, but it can affect other lists which aren't table. My suggestion is you edit your answer for made more specific. Like this => table tr.ui-sortable-helper { display: table !important; } – Rafael Gomes Francisco Dec 15 '15 at 20:44
  • Nice solution. Also, if you want your helper's cells to also be displayed identically, you have to set them a width. For instance, if you rely on the `DataTable` plugin to set your cell's widths, it won't work. – actaram Jul 19 '16 at 19:29
  • that doesnt work for me nor the other voted solution. any idea why? – Sagiv b.g Feb 06 '17 at 12:42
  • 5
    The fix doesn't work on tables with no defined width on the `td`. When the row is dragged, the `td` (with no width - inline or css) snaps/fits its width to its content. Maybe this is also your case @Sagivb.g – dunli Mar 07 '18 at 03:25
  • Thank you so much. – thedude12 Mar 13 '21 at 18:11
  • Awesome. Fixed! Expected ages passing by until solving this annoying issue. – maxischl Jun 19 '22 at 16:47
32

The selected answer here is a really nice solution, but it has one severe bug which is apparent in the original JS fiddle (http://jsfiddle.net/bgrins/tzYbU/): try dragging the longest row (God Bless You, Mr. Rosewater), and the rest of the cell widths collapse.

This means that fixing the cell widths on the dragged cell is not enough - you also need to fix widths on the table.

$(function () {
    $('td, th', '#sortFixed').each(function () {
        var cell = $(this);
        cell.width(cell.width());
    });

    $('#sortFixed tbody').sortable().disableSelection();
});

JS Fiddle: http://jsfiddle.net/rp4fV/3/

This fixes the problem of the table collapsing after you drag the first column, but introduces a new one: if you change the content of the table the cell sizes are now fixed.

To work around this when adding or changing content you would need to clear the widths set:

$('td, th', '#sortFixed').each(function () {
    var cell = $(this);
    cell.css('width','');
});

Then add your content, then fix widths again.

This still isn't a complete solution, as (especially with a table) you need a drop placeholder. For that we need to add a function on start that builds the placeholder:

$('#sortFixed tbody').sortable({
    items: '> tr',
    forcePlaceholderSize: true,
    placeholder:'must-have-class',
    start: function (event, ui) {
        // Build a placeholder cell that spans all the cells in the row
        var cellCount = 0;
        $('td, th', ui.helper).each(function () {
            // For each TD or TH try and get it's colspan attribute, and add that or 1 to the total
            var colspan = 1;
            var colspanAttr = $(this).attr('colspan');
            if (colspanAttr > 1) {
                colspan = colspanAttr;
            }
            cellCount += colspan;
        });

        // Add the placeholder UI - note that this is the item's content, so TD rather than TR
        ui.placeholder.html('<td colspan="' + cellCount + '">&nbsp;</td>');
    }
}).disableSelection();

JS Fiddle: http://jsfiddle.net/rp4fV/4/

Keith
  • 150,284
  • 78
  • 298
  • 434
  • The placeholder technique here is killer! Totally solved my problem. – jessegavin Feb 27 '14 at 05:31
  • 1
    This solution is actually really good, and the placeholder technique is really solid, this should be the selected answer in my opinion. – Chris James Champeau Mar 15 '16 at 00:43
  • 1
    This still causes unpleasant behavior when you have a very high row. All other rows will hide behind it. I would add this to the start method: `$(".must-have-class").css("height", $(ui.item).outerHeight()); ` – Itzik Ben Hutta Jan 15 '17 at 17:59
6

It's seems that cloning the row doesn't work well on IE8, but the original solution does.

Tested with the jsFiddle.

Mike Mackintosh
  • 13,917
  • 6
  • 60
  • 87
vincentp
  • 233
  • 4
  • 17
  • Thanks for setting up the jsFiddle. I'm going with the original solution because as Jez noted the other row cells may change width when dragging a row. – Roger Jun 20 '13 at 23:30
5

Call this following code when your table is ready to be sorted, this will make sure your td elements has a fixed with without breaking table structure.

 $(".tableToSort td").each(function () {
            $(this).css("width", $(this).width());
        });  
Teoman shipahi
  • 47,454
  • 15
  • 134
  • 158
  • Yes, that's pretty much the same as my answer, though mine works for `th` as well as `td`. It fixes the dragged row collapse and the table collapse, but at the cost of fixing the widths. – Keith May 28 '13 at 09:10
4

jsFiddle

After a lot of different attempts I just tried a simple solution which completes the solution of Dave James Miller to prevent the table of shrinking while dragging the largest row. I hope it will helps :)

// Make sure the placeholder has the same with as the orignal
var start = function(e, ui) {
    let $originals = ui.helper.children();
    ui.placeholder.children().each(function (index) {
        $(this).width($originals.eq(index).width());
    });
}
// Return a helper with preserved width of cells
var helper = function(e, tr) {
    let $helper = tr.clone();
    let $originals = tr.children();
    $helper.children().each(function (index) {
        $(this).width($originals.eq(index).outerWidth(true));
    });
    return $helper;
};
Ach
  • 81
  • 1
  • 6
2
.sortable({
    helper: function (e, ui) {
        ui.children().each(function () {
            $(this).width($(this).width());
        });
        return ui;
    }
});
mspiderv
  • 509
  • 7
  • 15
1

I've also search for a solution and finally it's here:

You have to add to your style

.ui-sortable-helper {
  display: table;
}

and when declare the sortable

$('#sortable').sortable({
  helper: function (e, tr){
      var myHelper = [];
      myHelper.push(<tr style="width:' + $('#sortable').first('tr').width() + '">);
      myHelper.push($(tr).html());
      myHelper.push('</tr>');
      return myHelper.join('');
    }
});

So you can set the percentage with of td without problems!

Cesarazzo
  • 21
  • 1
0

Keith' solution is fine but produced a little havoc in Firefox which did not add up the colspans but cued them. (The old js string type pain in the knee)

replacing this line:

 cellCount += colspan;

with:

 cellCount += colspan-0;

Fixes the problem. (As js is forced to treat the variables as numbers instead of strings)

Max
  • 2,561
  • 1
  • 24
  • 29
0

Dave James Miller's answer worked for me, but because of the layout of the container divs on my page, the helper that drags with the mouse cursor is offset from the position of my mouse. To fix that, I added the following to the helper callback

$(document.body).append($helper);

Here is the complete callback with the above line added:

helper: function (e, tr) {
  var $originals = tr.children();
  var $helper = tr.clone();
  $helper.children().each(function (index) {
    // Set helper cell sizes to match the original sizes
    $(this).width($originals.eq(index).width());
  });

  // append it to the body to avoid offset dragging
  $(document.body).append($helper);

  return $helper;
}

I would have added this as a comment to Dave's answer, but I did not have enough rep on this account.

Shea Riley
  • 31
  • 3
  • After reading the jQuery documentation, I found that the sortable also takes an "appendTo" option that specifies to what element the helper is appended. – Shea Riley Jan 09 '15 at 18:33
0

It seems like disableSelection() - method is bad and deprecated nowadays. I can't use text inputs inside sort-able row anymore in Mozilla Firefox 35.0. It just isn't focusable anymore.

Parkash Kumar
  • 4,710
  • 3
  • 23
  • 39
-2
$(function() {
    $( "#sort tbody" ).sortable({
        update: function () {
                                var order = $(this).sortable("toArray").join();
                                $.cookie("sortableOrder", order);
                        }
    });
    if($.cookie("sortableOrder")){
        var order = $.cookie("sortableOrder").split(",");
        reorder(order, $("#sort tbody"));
    }
    function reorder(aryOrder, element){
      $.each(aryOrder, function(key, val){
              element.append($("#"+val));
      });
    }
  });
Peter
  • 1
  • 1
-4

Apply the sortable to the table's tbody element and just set the helper to 'clone', as described in jquery-ui's API

$("$my-table-tbody").sortable({
    helper: "clone"
});
Regis Zaleman
  • 3,182
  • 6
  • 29
  • 30