Sortable lists with acts_as_list and ActiveAdmin
July 8th 2011For a small project I’m working on I wanted to give the client a nice admin interface, so I shopped around. My first attempt was with rails_admin, but I had issues installing it using the Rails 3.1 branch, and preferred to build this on edge Rails. I tried out ActiveAdmin and had found that Rails 3.1 support was already pretty much there.
The client wants a system of sortable ‘artworks’, and to achieve that through the ActiveAdmin interface was a bit of a challenge. I’ve done this sort of stuff before, but wrangling the ActiveAdmin interface into it was a different story, since it comes by default with tables instead of a unordered list, as most jQuery examples use. Through this issue I came across this useful tutorial which helped refresh me on the fundamentals.
The first issue I ran into was understanding how RailsAdmin loads its' javascripts. The active_admin.js manifest just requires “active_admin/base” from the gem, which is just a few lines of custom Javascript and vendored copies of Rails UJS, jQuery, and jQuery UI. I decided just to get rid of that and bring the gem’s code into mine, ditch Rails UJS (since I won’t be using it), and using the jQuery and jQuery UI from Rails' jquery-rails gem. So my active_admin.js looked like this:
// = require jquery
// = require jquery-ui
$(function(){
$(".datepicker").datepicker({dateFormat: 'yy-mm-dd'});
$(".clear_filters_btn").click(function(){
window.location.search = "";
return false;
});
});
I found this useful blog post about using jQuery UI’s sortable with a TABLE, as is the default in RailsAdmin. Implementing that, scoped to the default id of #artworks on my artworks index
function serializeArtworks(){
var artworkIds = $.makeArray(
$("#artworks .artwork").map(function(){
return $(this).data("id");
})
);
return {ids: artworkIds};
};
$("#artworks tbody").sortable({
update: function(){
$.ajax({
url: "/admin/artworks/sort",
type: 'post',
data: serializeArtworks(),
complete: function(){
$(".paginated_collection").effect("highlight");
}
});
}
});
My artwork objects only need to show a title and the default “show/edit/delete” buttons from RailsAdmin so my admin/artworks.js looks like this:
ActiveAdmin.register Artwork do
config.sort_order = 'position_asc'
index do
column :title do |artwork|
content_tag(:span, artwork.title, :"data-id" => artwork.id, :class => "artwork")
end
default_actions
end
collection_action :sort, :method => :post do
params[:ids].each_with_index do |id, index|
artwork = Artwork.find(id)
artwork.update_attribute(:position, index.to_i+1)
end
head 200
end
end
I provide the id of the artwork in an HTML5 data-attribute and access it with jQuery’s data method. It posts to a custom collection action, which sorts through the ids it receives and assigns the artworks a position corresponding to the order that they are received in. The hardest part for me was creating the serializeArtworks function, because of some bizarre shit involving jQuery’s map creating some special kind of array that refused to be converted to params by the $.param method that gets called on data being posted. The solution I ultimately went with was calling $.makeArray on the result of the map call, which gives you a pure Javascript array, which can be successfully turned into params.