2

I'm using the sortable function that comes with jQuery UI to enable me to reorder table rows. It's working fine, here is a JSFiddle and below is my HTML and JS

<table style="width: 100%;">
  <thead>
    <tr>
      <th>ID</th>
      <th>Fruit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>Apple</td>
    </tr>
      <tr>
      <td>2</td>
      <td>Pear</td>
    </tr>
    <tr class="sticky">
      <td>3</td>
      <td>Banana</td>
    </tr>
      <tr>
      <td>4</td>
      <td>Raspberry</td>
    </tr>
      <tr>
      <td>5</td>
      <td>Mango</td>
    </tr>
  </tbody>
</table>


$(function(){
    $("table tbody").sortable();
});

However I want to me able to drag multiple rows at the same time. Basically when you drag a tr if the tr directly below it has a class of sticky then that tr needs to be dragged with it. So in my example any time you want to drag and reorder the "Pear" row the "Banana" row underneath will be moved with it.

I think it's possible to do this with a helper function but I'm really not sure where to start. If someone could advise me on the best way to do it and point me in the right direction that would be great.

3
  • I have a solution that works, but it doesn't animate the "sticky" tr. Do you want the sticky item to be animated with the dragged item? Commented Mar 5, 2016 at 21:53
  • @Yass ideally yes, but a solution that doesn't animate is better than no solution. If you show what you've done so far I might be able to do the animation part Commented Mar 5, 2016 at 22:11
  • Okay. I've sorted the animation aspect as well. Commented Mar 5, 2016 at 22:14

3 Answers 3

4

You can use the start event to check if the next tr has a class of sticky and insert it after the dragged item on the stop event. I'm making use of the css method to animate the "sticky" item.

$(function() {
  var $draggedItem, $stickyItem, itemWidth, itemHeight;
  $("table tbody").sortable({
    start: function(event, ui) {
      $draggedItem = $(ui.item[0]);
      //Store the constant width and height values in variables.
      itemWidth = $draggedItem.width();
      itemHeight = $draggedItem.height();
      //next is called twice because a hidden "tr" is added when sorting.
      var $nextChild = $(ui.item[0]).next().next();
      if ($nextChild.hasClass('sticky')) {
        $stickyItem = $nextChild;
      }
      else {
        $stickyItem = undefined;
      }
    },
    sort: function() {
      if ($stickyItem != undefined) {
        //Set the css to match the dragged item's css, with the exception of "top", which includes an offset.
        $stickyItem.css({
          "z-index": $draggedItem.css('z-index'),
          "position": "absolute",
          "width": itemWidth,
          "height": itemHeight,
          "left": $draggedItem.css('left'),
          "top": $draggedItem.position().top + itemHeight,
        });
      }
    },
    stop: function() {
      if ($stickyItem != undefined) {
        $stickyItem.insertAfter($draggedItem);
        //Remove the style attribute to reset to thed default style.
        $stickyItem.removeAttr('style');
      }
    }
  });
});

Fiddle Demo

Note: You could improve the above further by only binding the sort and stop events if a sticky item exists.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks a lot for your answer. I had to add to a bit to make sure that you couldn't drag another row between a sticky row and the one above it. But your well commented solution made it easy for me to understand
@Pattle Can you please share those lines which you have added!
@Pattle Share those extra lines of code which you have added please! I'm stuck in a similar situation :(
@Pattle c'mon! Show what you changed please. I'm having the same issue.
1

For those of you who'd like to prevent inserting rows between the sticky row and the one above it, here's a slightly modified version of Yass's solution:

$(function () {
    let $draggedItem, $stickyItem, itemWidth, itemHeight, cancelSortable;
    $("table tbody").sortable({
        start: function (event, ui) {
            $draggedItem = $(ui.item[0]);
            //Store the constant width and height values in variables.
            itemWidth = $draggedItem.width();
            itemHeight = $draggedItem.height();
            //next is called twice because a hidden "tr" is added when sorting.
            let $nextChild = $(ui.item[0]).next().next();
            if ($nextChild.hasClass('sticky')) {
                $stickyItem = $nextChild;
            } else {
                $stickyItem = undefined;
            }
        },
        sort: function (event, ui) {
            if ($stickyItem !== undefined) {
                //Set the css to match the dragged item's css, with the exception of "top", which includes an offset.
                $stickyItem.css({
                    "z-index": $draggedItem.css('z-index'),
                    "position": "absolute",
                    "width": itemWidth,
                    "height": itemHeight,
                    "left": $draggedItem.css('left'),
                    "top": $draggedItem.position().top + itemHeight,
                });
            }
            if (ui.placeholder.next() !== undefined && ui.placeholder.next().hasClass("sticky")) {
                ui.placeholder.hide();
                cancelSortable = true;
            } else {
                ui.placeholder.show();
                cancelSortable = false;
            }
        },
        stop: function () {
            if (cancelSortable) {
                $(this).sortable("cancel");
            }
            if ($stickyItem !== undefined) {
                $stickyItem.insertAfter($draggedItem);
                //Remove the style attribute to reset to the default style.
                $stickyItem.removeAttr('style');
            }
        }
    });
});

Comments

0

sortable options to group more than two rows (edit the lines in "start" parameter that uses next() two times in a row) :

let $nextChildren = $(ui.item[0]).nextAll(".sticky:visible").not(".ui-sortable-placeholder");
if ($(ui.item[0]).next().next().hasClass('sticky')) {
$stickyItem = $nextChildren;
}

this codes takes all the next rows that have "sticky" class, even if there are rows without the class between them.

1 Comment

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.