Dragging rows between tables

Brian picture Brian · Nov 20, 2014 · Viewed 8.2k times · Source

The idea is pretty simple and it almost works. There are two tables and the user has the option of dragging rows between the two tables. When a row is dragged from table1 to table2, ajax is used in order to update the database with the data that is removed from table1, added to table2, and to re-display both of the tables with the new data. The same thing works if information is dragged from table2 to table1.

You can see a sample of the code here.

Here is an excerpt of the Javascript code for one of the tables:

var startTable = "table1";
var $tabs=$("#" + startTable);
$( "tbody.connectedSortable")
.sortable({
    connectWith: ".connectedSortable",
    items: "> tr:not(:first)",
    appendTo: $tabs,
    helper:"clone",
    cursor:"move",
    zIndex: 999990
})
.disableSelection()
;
$($tabs).droppable({
    accept: ".connectedSortable tr",
    hoverClass: "ui-state-hover",
    drop:function(event, ui){
        var start= ui.draggable.attr("id");
        var desTable = $(this).attr("id");
        if(start != desTable){
            alert("The ajax should be called");
        }
        return false;
    }
});

It works perfectly except for just one case. If a row is being dragged from Table1 to Table2, it creates a slot to show where the row will be inserted when the row is let go. In other words, if a user drags a row from Table1 to the last element of Table2, it creates an open place-holder (under the last row in Table2) to depict where the row will go when let go. There is one problem with this. If the place-holder is created but the row is then dragged outside of the table and let go, the row still goes to the place-holder but the draggable property is never called.

What I would like to happen is if a place-holder is created, no matter where the row is let go, it will go to the place-holder and call the droppable code that corresponds to the table it was dropped in. If no place-holder is present, the row should go back to where it was dragged from and nothing should happen.

Every example I've tried that drags rows between two tables has the same problem. Do you guys have any way to call the droppable code even if the row is being dropped outside of the table? Or maybe there is a better way to call ajax rather than when the row is dropped on the table? Any insight would be greatly appreciated.

Answer

T J picture T J · Nov 24, 2014

For triggering an ajax request when a row is dropped from one table to another you can use the receive event of sortable widget.

This event is triggered when an item from a connected sortable list has been dropped into another list. The latter is the event target.

(emphasis mine)

Updated Fiddle (Partial result, See the below snippet for final demo)

Inside the receive callback, you can access the dropped row using the item property of second argument (ui.item).

If the receive event callback is triggered, it means ui.itemhas been added to this table ($(this).closest("table.mytable")) and removed from the other table ( $("table.mytable").not($(this).closest("table.mytable"))). You can then trigger the ajax request(s) accordingly.

By doing it this way, you don't have to manually check whether drop happened within the same table or not (You'll have to do this if you're using update event as someone suggested).


At the moment, you're unnecessarily initializing the sortable twice with:

$( "tbody.connectedSortable").sortable({
    connectWith: ".connectedSortable",
    items: "> tr:not(:first)",
    appendTo: $tabs,
    helper:"clone",
    cursor:"move",
    zIndex: 999990
})

and

$("tbody.connectedSortable").sortable({
    connectWith: ".connectedSortable",
    items: "> tr:not(:first)",
    appendTo: $tabs2,
    helper:"clone",
    cursor:"move",
    zIndex: 999990
});

The selector tbody.connectedSortable is applicable to both tables, hence it'll simply override the previous initialization, As the result, the clone helper will be always appended to the second table ($tabs2). It is probably not what you want - from the looks of it you're initializing twice just for appending the clone to respective parent. The default value of appendTo option is "parent", just removing it from initialization will do the job.

Also, it is a good idea to move the header rows from <tbody> into <thead> element so that you can avoid specifying items: "> tr:not(:first)" : it is more semantic as well as slightly better for performance since jQuery UI doesn't have to search for invalid items if that option is not specified.

Finally, you've duplicate id's which is invalid. For grouping a set of elements, use a common class instead.


$(document).ready(function() {
  $("tbody.connectedSortable").sortable({
    connectWith: ".connectedSortable",
    helper: "clone",
    cursor: "move",
    zIndex: 99999,
    receive: function(event, ui) {
      /* here you can access the dragged row via ui.item
         ui.item has been removed from the other table, and added to "this" table
      */
      var addedTo = $(this).closest("table.mytable"),
        removedFrom = $("table.mytable").not(addedTo);
      alert("The ajax should be called for adding to " + addedTo.attr("id") + " and removing from " + removedFrom.attr("id"));
    }
  });
});
.mytable a:link,
.mytable a:visited {
  color: #fff;
  font-weight: bold;
  text-decoration: none;
}
.mytable a:active,
.mytable a:hover {
  color: #bd5a35;
  text-decoration: underline;
}
table.mytable {
  width: 90%;
  font-family: Arial, Helvetica, sans-serif;
  color: #666;
  margin-left: auto;
  margin-right: auto;
  font-size: 12px;
  background: #eaebec;
  border: #ccc 1px solid;
  -moz-border-radius: 3px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
  -moz-box-shadow: 10px 10px 5px #888;
  -webkit-box-shadow: 10px 10px 5px #888;
  box-shadow: 10px 10px 5px #888;
}
.mytable th {
  color: #fff;
  padding: 21px 25px 22px 25px;
  border-top: 1px solid #fafafa;
  border-bottom: 1px solid #e0e0e0;
  background: #191970;
}
.mytable th:first-child {
  text-align: center;
  padding-left: 20px;
}
.mytable tr {
  text-align: center;
  padding-left: 20px;
}
.mytable tr td:first-child {
  text-align: center;
  padding-left: 20px;
  border-left: 0;
}
.mytable tr td {
  padding: 18px;
  border-top: 1px solid #ffffff;
  border-bottom: 1px solid #e0e0e0;
  border-left: 1px solid #e0e0e0;
  background: #fafafa;
  background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafa fa));
  background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
}
.mytable tr.even td {
  background: #f6f6f6;
  background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#f6f6 f6));
  background: -moz-linear-gradient(top, #f8f8f8, #f6f6f6);
}
.mytable tr:last-child td {
  border-bottom: 0;
}
.mytable tr:last-child td:first-child {
  -moz-border-radius-bottom-left: 3px;
  -webkit-border-bottom-left-radius: 3px;
  border-bottom-left-radius: 3px;
}
.mytable tr:last-child td:last-child {
  -moz-border-radius-bottom-right: 3px;
  -webkit-border-bottom-right-radius: 3px;
  border-bottom-right-radius: 3px;
}
.mytable tr:hover td {
  background: #f2f2f2;
  transform: scale(1.01);
  padding-left: 20px;
  outline: 1px solid #191970;
  -moz-box-shadow: 10px 10px 5px #888;
  -webkit-box-shadow: 10px 10px 5px #888;
  box-shadow: 10px 10px 5px #888;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<table id='table1' class="mytable">
  <thead>
    <tr class="table1">
      <th>col1</th>
      <th>col2</th>
      <th>col3</th>
      <th>col4</th>
    </tr>
  </thead>

  <tbody class="connectedSortable">

    <tr class="table1">
      <td>a</td>
      <td>b</td>
      <td>c</td>
      <td>d</td>
    </tr>
    <tr class="table1">
      <td>e</td>
      <td>f</td>
      <td>g</td>
      <td>h</td>
    </tr>
  </tbody>
</table>
<table id='table2' class="mytable">
  <thead>
    <tr class="table2">
      <th>COL1</th>
      <th>COL2</th>
      <th>COL3</th>
      <th>COL4</th>
    </tr>
  </thead>
  <tbody class="connectedSortable">

    <tr class="table2">
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
    </tr>
    <tr class="table2">
      <td>5</td>
      <td>6</td>
      <td>7</td>
      <td>8</td>
    </tr>
  </tbody>
</table>

Side note: I've combined the similar CSS classes

The disableselection() method is deprecated from jQuery UI 1.9+