The ability to order lists is a popular feature for many web apps. We want to accomplish it with a minimum of overheads, dependencies and fuss. With Ruby on Rails and modern browsers - this is a simple task. 

Step 1, The Setup.

We will need to manage the ordering of the items in the database. Rails has a gem for that. Add https://github.com/swanandp/acts_as_list to your gemfile.

This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list.

The class that has this specified needs to have a position column defined as an integer on the mapped database table. Now add the position integer to the model we are ordering

class AddPositionToSpans < ActiveRecord::Migration[5.2]
  def change
    add_column :spans, :position, :integer
  end
end

 Add the macro to the model being ordered. Lists can be scoped to their belongs_to record. This means the list order numbers will only be updated in relation to other records that belongs_to their circuit in this example.


acts_as_list scope: :circuit
default_scope { order(position: :asc) }

If you have existing records, it's now a good time to set the order to start with. A migration is good for this.


class SetSpanLists < ActiveRecord::Migration[5.2]
  def change
    Circuit.all.each do |circuit|
      circuit.spans.order(:external_reference).each.with_index(1) do |span, index|
        span.update_column :position, index
      end
    end
  end
end

Step 2. The Front End.

Rather than using a large library like JqueryUI for this, we can now use this simple plugin that gets the job done with a lot less code and overheads. 

Lightweight vanillajs micro-library for creating sortable lists and grids using native HTML5 drag and drop API.

https://github.com/lukasoppermann/html5sortable

Download a copy of the js from this repo, I like to use the uncompressed dist copy as Rails will compress the code for production and it means I'm dealing with js code I can read in case something goes wrong and I need to understand what's happening. 

Step 3, Hooking It All Together.

Now we need to add some code to tell the plugin which list is orderable and what to do when the list is reordered. Add this to the table:

<tbody id= "spans" data-url = "<%= sort_spans_path %>">

And then this to your applications js file.


$(document).on('turbolinks:load', function() {
  sortable('#spans', {
    items: 'tr'
  });
  if (typeof sortable('#spans')[0] != 'undefined'){
    sortable('#spans')[0].addEventListener('sortupdate', function(e) {
      var dataIDList = $(this).children().map(function(index){
         $(this).find( ".position" ).text(index + 1)
         return "span[]=" + $(this).data("id");
      }).get().join("&");
      Rails.ajax({
          url: $(this).data("url"),
          type: "PATCH",
          data: dataIDList,
        });
    });
  }
})


This tells the plugin:

  • That the table body with id "spans" is orderable.
  • Listens to the sortable for spans to update
  • Builds a serialised list of the span ids
  • Sends the list to the controller action for saving.

Step 4, The Final Bit.

So now we need to create a route and a controller method so we can patch the data to it and update the records. Add this to your routes:

resources :spans do
  collection do
    patch :sort
  end
end

Now, add this, in it's simplest form, this to your controller:


def sort
  params[:span].each_with_index do |id, index|
     Span.where(id: id).update_all(position: index + 1)
  end
  respond_to do |format|
      format.js
  end
end

Finally, you will want some visual queue that the update has happened, so add a sort.js.rb to the spans/views


$("#span-messages").html('Spans updated');
$("#span-messages").highlight(1000);

And ta da. A sortable list. Acts as list has a lot of nice methods to navigate the list and move items around.  Html5Sortable can use nested lists, choose placeholders and drag handles and accept max item numbers etc.  It's pretty flexible.  Happy hacking.