If you need to make a Rails resource type re-orderable, you have a couple of options in gem-land. acts_as_list is probably the most popular of these. My problem with this gem is this method..
# This has the effect of moving all the lower items down one.defincrement_positions_on_lower_items(position)acts_as_list_class.unscoped.update_all("#{position_column} = (#{position_column} + 1)","#{scope_condition} AND #{position_column} >= #{position}")end
To change one item’s position, all items in the list that follow it must have their position updated also. Once you have more than a few items in your database, changing their order ends up being very expensive and slow. There has to be a better way!
So, ranked-model only performs an update operation on the item in question. It achieves this by creating a rank number that is midway between the two neighbors, these numbers are spread out across a very wide range; -8388607 to 8388607 (the extents of a signed MEDIUMINT in MySQL.
What happens if, after a few thousand re-sorts on a list there is actually an overlap with rank-numbers, or they end up out of range? Well, in that rare scenario the whole table will be updated..
This is much more elegant. Realistically speaking, unless you’re dealing with thousands of items your web-app won’t suffer the problems that this gem solves. However, ranked-model is so easy to set up that I can’t see any good reason not to use it.
Twitter Bootstrap
There are a few ways to add bootstrap into your project, you could find a non-less version, i.e. css, and copy in the files or you could use one of the many available gems. Most of them are engines, some of them use the original less and rely on the less-rails gem to translate it. I often use compass in Rails apps, so it would seem that the compass-twitter-bootstrap gem is a good option.
Table Markup
Now you can add your bootstrap markup for a table to the index view-template, (sorry haml-haters!).
views/admin/things/index.html.haml
12345678910111213141516
%table.table.table-bordered.table-striped%thead%tr%th=Title%th=Description%th
%tbody-@things.eachdo|thing|%tr%td=thing.title%td=thing.description.truncate(20)%td=link_to'show',admin_thing_path(thing),:class=>'btn'=link_to'edit',edit_admin_thing_path(thing),:class=>'btn btn-primary'=link_to'destroy',admin_thing_path(thing),method::delete,confirm:"Are you sure?",:class=>'btn btn-danger'
Sort Action
Next you’ll need to add the #sort action to your controller..
controllers/admin/things_controller.rb
123456789101112131415
classAdmin::ThingsController<Admin::ApplicationControllerdefsort@thing=Thing.find(params[:id])# .attributes is a useful shorthand for mass-assigning# values via a hash@thing.attributes=params[:thing]@thing.save# this action will be called via ajaxrendernothing:trueendend
jQuery-># this is a small hack; when a tr is dragged with jQuery UI sortable# the cells lose their widthcells = $('.table').find('tr')[0].cells.lengthdesired_width = 940/cells+'px'$('.table td').css('width',desired_width)$('#sortable').sortable(axis: 'y'items: '.item'# highlight the row on drop to indicate an updatestop: (e, ui) ->ui.item.children('td').effect('highlight',{},1000)update: (e, ui) ->item_id = ui.item.data('item_id')position = ui.item.index()$.ajax(type: 'POST'url: $(this).data('update_url')dataType: 'json'# the :thing hash gets passed to @thing.attributes# row_order is the default column name expected in ranked-modeldata: {id: item_id,thing: {row_order_position: position}}))
Next we need to add into the markup the data expected by the Javascript.
views/admin/things/index.html.haml
1234567891011121314151617181920
# the update url is passed via an html5 data attribute
%table.table.table-bordered.table-striped#sortable{:data=>{update_url:admin_sort_thing_path}}
%thead%tr%th=Title%th=Description%th
%tbody-@things.eachdo|thing| # make the <tr>'s draggable by adding the expected '.item' class
# pass the item id into params[:id]
%tr{data:{item_id:"#{thing.id}"}, class: 'item'}
%td=thing.title%td=thing.description.truncate(20)%td=link_to'show',admin_thing_path(thing),:class=>'btn'=link_to'edit',edit_admin_thing_path(thing),:class=>'btn btn-primary'=link_to'destroy',admin_thing_path(thing),method::delete,confirm:"Are you sure?",:class=>'btn btn-danger'
Finally, to indicate that the items are re-orderable I chose to change the cursor to row-resize pointer.
Comments