23

I have two connected tbody elements allowing me to drag rows between two tables. Everything works fine until all rows are removed from either table.

When all rows have been dragged to the other table the tbody height decreases making it (near)impossible to drop rows back inside.

Is there a known workaround for this problem? (min-height doesn't work on a tbody element)

Many thanks in advance.

Josh Darnell
  • 11,304
  • 9
  • 38
  • 66
  • Good question, I am trying to sort rows amd move lists and get the same issue with empty tables...Did you get any further with this? – Mark Redman Oct 26 '10 at 12:42
  • Solution here: http://stackoverflow.com/questions/6832687/jquery-problem-with-sortable-items-cant-be-dropped-onto-empty-lists Summary: Add padding to the container – Populus Jan 29 '13 at 16:43

12 Answers12

15

What you can do is create a row that is invisible to the "sortable" mechanism. Probably the easiest way to do this is with the "items" option.

Let's say your HTML looks like this

<tbody class="sortable">
    <tr><td>stuff</td></tr>
    <tr><td>stuff</td></tr>
    <tr><td>stuff</td></tr>
    <tr class="sort-disabled"><td></td></tr>
</tbody>

Then in jquery you can have

$('.sortable').sortable({
    items: ">*:not(.sort-disabled)"
});

It's a bit of a hack, but I think if you play around with variations of this (give the .sort-disabled row some height in CSS etc.) you can probably find something that works for you. You could also try having a .sort-disabled row both first and last, so that the place in the middle is the drop target.

Hope this helps!

Danny Roberts
  • 3,442
  • 23
  • 28
  • Ended up doing this. Worked very well. I did just "tr:not(.padder)" since I believe the ">" is implicit. – Henrik N Mar 15 '12 at 09:16
  • Actually, I found one annoying downside: you can't guarantee whether you'll drop items before or after the padder row. That means the styling can get inconsistent, especially if you have multiple tables where some have rows above the padder and some have rows below it. – Henrik N Mar 15 '12 at 09:24
  • 1
    Turns out if you put the padder (`.sort-disabled`) as the first element, the above is not a problem. With an empty list, you can only drop rows below the padder, not above it. So if the padder is first, it'll stay first. If it's last, it will change position, making your whitespace inconsistent. – Henrik N Mar 15 '12 at 09:33
  • Problem with inconsistent white space can be solved with `.sort-disabled:only-child td {height:.5rem}`. White space only shows when table is empty. – Ajax Dec 28 '18 at 12:38
8

It's hard to force table esp. tbody to have height while it's empty. So I did it in following way.

<div class="ui-widget sortablecolumn">
   <table>
   </table>
</div>  

$( '.sortablecolumn').sortable(
{
   connectWith: '.sortablecolumn',
   items: 'table > tbody > *',
   receive: function(ev, ui) {
        ui.item.parent().find('table > tbody').append(ui.item);
   }
});
$( '.sortablecolumn' ).disableSelection();

Key aspects are items selector and receive event handler where added item is moved into table body.

Now it works fine.

cetnar
  • 9,357
  • 2
  • 38
  • 45
  • This is the best answer. No hacks, just another container div and solved. Thanks! – MazarD May 31 '16 at 15:26
  • 1
    I solved my issue by using `var $parent = ui.item.parent(); if($parent.is('table')){$parent.find('tbody').append(ui.item);}` instead, which is very close to what you have. – Ismael Miguel Mar 15 '17 at 12:51
4

Checkout my post about this - you can solve it by attaching a method to the click which adds height to empty containers:

function sortAndDrag() {
 
    //show BEFORE sortable starts
     $(".sortableClass").bind('click mousedown', function(){
          $(".sortableClass").each(function (c) {
                if ($("tbody", this).size() == 0) {
                    $(this).addClass("aClassWhichGivesHeight")
                }
            })
     });
 
    //enable sortable
    $(".sortableClass").sortable({
        connectWith: ".sortableClass",
        stop: function (a, d) {
            $("tbody").removeClass("aClassWhichGivesHeight");
        }
    });
 
}

Something like that?

Meloman
  • 3,558
  • 3
  • 41
  • 51
Woody
  • 49
  • 1
  • 1
  • Adding height to tbody doesn't help as tbodys are not shown without content - even with `display: table-row-group` or similar being set. – Deebster Mar 02 '13 at 20:43
3

I have the same question, and managed to half solve it by doing this:

$('table').sortable(
{
    connectWith: 'table',
    items: 'tbody tr'
});

This allows me to move rows on to an empty table, and it looks fine, but the element is inserted after the tbody instead of inside it. I think Danny Robert's answer will work for me but I'd be interested in seeing a non hack solution.

Matt
  • 3,778
  • 2
  • 28
  • 32
2

Here's how I solved the issue with being unable to drop a tr in an empty tbody:

$(function() {

    var sort1 = $('#sort1 tbody');
    var sort2 = $('#sort2 tbody');

   sizeCheck(sort1);
   sizeCheck(sort2);

   //Start the jQuery Sortable for Active and Fresh Promo Tables
   $("#sort1 tbody, #sort2 tbody").sortable({

     items: ">*:not(.sort-disabled)",

     deactivate: function(e, ui) {

        sizeCheck(sort1);
        sizeCheck(sort2);

     },
     //Connect tables to pass data
     connectWith: ".contentTable tbody"

   }).disableSelection();

});

//Prevent empty tbody from not allowing items dragged into it

function sizeCheck(item){
    if($(item).height() == 0){
        $(item).append('<tr class="sort-disabled"><td></td></tr>');
    }
}
Tisho
  • 8,320
  • 6
  • 44
  • 52
Devin Walker
  • 540
  • 3
  • 8
2

I know this question has been marked as "answered" but I also wanted to add another way to approach this.

What I do is check if the list empty, if so, create a new row element and inject it into the tbody. I put a message like "No more items" in the td.

Once an item is dropped into the "empty" list, the empty message will be destroyed.

I use Mootools hence the lack of example code.

user564448
  • 387
  • 3
  • 9
1

js

$(".sortable").sortable({
    items: 'tbody > tr',
    connectWith: ".sortable"
});

css

.sortable {
    background-color: silver;    
    height: 10px;
}

.sortable td { background-color: white; }

html

<table class='sortable'>    
    <tbody>
        <tr><td colspan="2" class="drop-row" style="display: none;"></td></tr>
    </tbody>    
</table>

More details and even a better code at https://devstuffs.wordpress.com/2015/07/31/jquery-ui-sortable-with-connected-tables-and-empty-rows/

Adauto
  • 569
  • 3
  • 5
1

Easy solution (PURE CSS):

tbody:after {
    content:" ";
    height:30px;
}

Where the blank space is not a stardard blank space. It's an invisible blank character like the following: Invisible characters - ASCII

Worked for me in FF and Chrome.

Community
  • 1
  • 1
Becario Senior
  • 704
  • 10
  • 18
0

i use:

$(document).ready(function(){
      $( "#sortable1 tbody, #sortable2 tbody" ).sortable({
      connectWith: ".connectedSortable",
      remove: function(event, ui){ if($(this).html().replace(/^\s+/, "") == '') { $(this).html('<tr class="tmp_tr nobr"><td colspan="7">&nbsp;</td></tr>'); }},
      update: function( event, ui ) {$(this).find('.tmp_tr').remove();},
    }).disableSelection();

});

0

In my case it was about table and tbody collapsing to size 0x0 when no items present. What worked was providing some minimal size for the table and tbody, like this:

table.items-table {
    width: 100%; /*needed for dropping on empty table to work - can be any non-zero value*/
}

table.items-table >tbody { /*needed for dropping on empty table to work */
    display: block;
    min-height: 10px;
}

That was all that was needed.

ed22
  • 1,127
  • 2
  • 14
  • 30
0

Based on Ismael Miguel comment on Cetnar answer, here is my working solution.

  • I send different ajax call for each table (urgent, normal, low priority tasks), and it works fine with empty table dropping.
  • In database I update all tasks present in the array sent by ajax with a sorting column.

3 tables sortable with tr and sorted in database

jQuery code :

$('.draggable-zone').sortable({
    items:       'tr.draggable',
    helper:      'clone',
    appendTo:    'body',
    connectWith: '.draggable-zone',
    update: function(e, ui) {
        $.ajax({
            url:  '/inve/new/sort', 
            type: 'POST',
            data: $(this).sortable('serialize', { 
                key: $(this).attr('data-partl')
            })
        });
    },
    receive: function(e, ui) {
        var $parent = ui.item.parent(); 
        if($parent.is('table')){
            $parent.find('tbody').append(ui.item);
        }
    }
});

HTML / Smarty template (1 table only) :

<table class="table table-striped table-hover table-bordered draggable-zone" data-partl="normal[]">
    <tbody>
        {foreach $data.normal as $task}
            <tr class="draggable" id="sort_{$task.id}">
                <td><b>{$task.title}</b></td>
                ...
            </tr>
        {/foreach}
    </body>
</table>
...
Meloman
  • 3,558
  • 3
  • 41
  • 51
0

I solved this basically copying the jqueryui sortable demo, i saw that they added a 5px padding to their empty list

https://jqueryui.com/sortable/#empty-lists

i added 1px padding, it's not naked-eye-appreciable but it's enough to enable the drop zone placeholder trigger, which allows me, that is to say, to drop.

orenishi
  • 1
  • 1
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 19 '23 at 22:26