javascriptjqueryjquery-ui

Sorting multiple table rows with jQuery UI sortable


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.


Solution

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