UI Sortable on table rows with dynamic height
UI sortable helps reordering items with drag 'n drop. It works quite fine.
Proven configuration for sorting table rows
When invoking the plugin, you may pass several options Show archive.org snapshot . This set is working fine with table rows:
$tbody.sortable # Invoke on TBODY when ordering tables
axis: 'y' # Restrict drag direction to "vertically"
cancel: 'tr:first-child:last-child, input' # Disable sorting a single tr to prevent jumpy table headers
containment: 'parent' # Only drag within this container
placeholder: 'ui-sortable-placeholder' # Prevent visibility:hidden
stop: onSortEnd # See below
start: onSortStart # See below below
Inside onSortEnd()
, you'll react on the reordering. Within forms, you may want to update hidden position fields in the form:
onSortEnd = ->
positionFields = element.find('input[id$=position]')
for position, i in positionFields
$(position).val i + 1
Outside of forms, within lists, you may want to manually post the new positions to some URL:
onSortEnd = ->
ordered_ids = element.sortable('serialize')
$.post '/some/url', ordered_ids
The serialize
method aggregates the id's of all sortable items (i.e. table rows), which are expected in format <set name>_<id>
. For example, tr#position_13
and tr#position_15
will be turned into position[]=13&position[]=15
, which neatly resolves to params[:position] == [13, 15]
in Rails.
Broken placeholder height
When a dragged row does not have a set height but rather adjusts to its content, the placeholder row does not get the height of the currently dragged item. Further, when using "containment" (which limits dragging to within a given container), it uses wrong containment limits.
You can fix this in the onSortStart
callback:
onSortStart = (event, ui) ->
sortable = $(this).sortable('instance') # Obtain UI sortable instance
draggedItemHeight = ui.helper.height()
draggedItemHeight -= 1 if ui.helper.is('tr:first-child') # Design fix
# Fix placeholder height
sortable.containment[3] += (draggedItemHeight - ui.placeholder.height())
ui.placeholder.height draggedItemHeight
Styling
Finally, here are some subtle, but nice styles:
.ui-sortable-handle
&:hover
cursor: grab
// Applied during the drag
&.ui-sortable-helper
display: table // Maintain table cell widths
opacity: 0.7
transition: opacity 0.3s
cursor: grabbing
// Fix design issue with UI sortable, causing the thead border-bottom to grow
// when the first table row is dragged
tr.ui-sortable-helper:first-child + tr > td
border-top: none