We start with a simple HTML table:
<table class="sortable">
<tbody>
<tr data-id="1">
<td>Some content</td>
<td>More content</td>
</tr>
<tr data-id="1">
<td>Does not matter</td>
<td>Any table will do</td>
</tr>
<tr data-id="1">
<td>There are only two requirements:</td>
<td>The table needs to have a tbody-tag. And every row should have an data-id-attribute.</td>
</tr>
</tbody>
</table>
Then we add some JavaScript:
$(document).ready(function() {
$('table.sortable').each(function() {
$(this).children('thead, tfoot').children('tr').prepend('<th></th>');
var $sortable = $(this).children('tbody');
$sortable.children('tr').prepend('<td><i class="fa fa-arrows" unselectable="on"></i></td>');
$sortable.on('selectstart', '.fa-arrows', function(e) {
e.preventDefault();
return false;
}).on('mousedown', '.fa-arrows', function(e) {
e.preventDefault();
var $dragged = $(this).parent().parent();
var $ghost = $dragged.clone();
$ghost.wrapAll('<table class="ghost"><tbody></tbody></table>');
$ghost.children('td').each(function() {
$(this).width($dragged.children().eq($(this).prevAll().size()).width());
});
$ghost = $ghost.parent().parent();
$ghost.css({
left: $sortable.parent().offset().left + 'px',
top: $dragged.offset().top + 'px'
});
$sortable.parent().after($ghost);
$dragged.addClass('dragged');
$dragged.data({
offset: e.pageY - $dragged.offset().top,
moved: false
});
$(document).one('mouseup', function(e) {
e.preventDefault();
$sortable.parent().next('table.ghost').remove();
$(document).off('mousemove.dragging');
var $dragged = $sortable.children('tr.dragged');
if($dragged.data('moved')) {
var positions = [];
$sortable.children('tr').each(function() {
positions.push($(this).data('id'));
});
$.ajax(document.location.href, {
data: {
positions: positions
},
type: 'POST'
});
}
$dragged.removeClass('dragged').removeData('offset moved').css('top', 'auto');
});
$(document).on('mousemove.dragging', function(e) {
e.preventDefault();
var $dragged = $sortable.children('tr.dragged');
var mouse = e.pageY - $dragged.data('offset');
var $ghost = $sortable.parent().next('table.ghost');
$ghost.css('top', mouse + 'px');
var $next = $dragged.next('tr:not(.ghost)');
if($next.size() && $next.offset().top < mouse) {
$next.after($dragged);
$dragged.data('moved', true);
}
var $prev = $dragged.prev('tr:not(.ghost)');
if($prev.size() && $prev.offset().top > mouse) {
$prev.before($dragged);
$dragged.data('moved', true);
}
});
});
});
});
Optionally some (S)CSS to make things prettier:
table {
&.sortable, &.ghost {
.fa-arrows {
cursor: pointer;
cursor: -webkit-grab;
cursor: -moz-grab;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
&:active {
cursor: pointer;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
}
tbody {
td:first-child {
width: 20px;
text-align: center;
}
tr.dragged {
opacity: .5;
}
}
& + table.ghost {
@include box-shadow(0px 0px 2px rgba(0, 0, 0, .5));
position: absolute;
td {
background: #ffffff;
}
}
}
}
By the way, I am using Font Awesome to show an icon.
Now, when the user drags a row of the table and changes the sorting of the rows, an array containing the row's ids in the new order is send to the current url. There you can simply catch them via PHP for example and save the new sorting.