JQuery UI has a great widget called Sortable, where you can drag and drop items in place to… you guessed it, it sorts the items. This is great and all, but one of the reasons you may want to have this interactive UI is to reorder some list the user owns. In this post, we’ll take a look at one implementation of persisting newly sorted items in order to the database. This method will not use AJAX to submit the form.

If you want to follow along, go ahead and fork this Github repo. I’ve created branches for the major checkpoints.

Setup

Let’s pretend that we’re making a photo album application, where we have photos that belong to an album. We want to show the photos in a particular order so when we edit the album, we should be able to use the Sortable widget and submit a form to persist the new order into the database.

I’ve set up the bare-bones configuration before we actually start implementing the Sortable widget. This can be found on the GitHub repo as branch “1_base_config”. It’s a very standard Rails MVC setup except for that photo is a nested resource under album. As a result, in order to create any photos, you must create an album first, then add photos. I recommend forking, running the rails server, and test out the flow of the resources.

Before_create hooks

Note that we have an order attribute and a token attribute on the Photo model. The order is somewhat self-explanatory, but we’ll see soon why we need the token attribute. Both of these fields should be auto generated by the system when the photo is created.

First, open up the model folder and find the Photo.rb file. We’re going to create 2 private methods that will automatically generate the order and token on the “before create” hook.

# Photo.rb
class Photo < ActiveRecord::Base
  belongs_to :album
  before_create :generate_token, :generate_order

  private
    def generate_token
      self.token = loop do
        random_token = SecureRandom.urlsafe_base64(nil, false)
        break random_token unless self.class.exists?(token: random_token)
      end
    end

    def generate_order
      self.order = Product.maximum(:order) ? Product.maximum(:order) + 1 : 1
    end
end

In general, these methods should be broken out to different concerns so that it’s reusable across model, but we’ll save that for another blog post. You can test out that these hooks are working by starting the Rails server and creating some photos.

Sortable Widget

The next step is to implement the sortable widget. For this example, we’ll use just the photo description, but you can imagine that the actual image could be used as part of the UI. Additionally, as part of the configuration, the JQuery UI library has already been included in the vendor/assets folder and the application.

First, setup a list item of all the album photos in views/albums/edit.html.erb. In order to turn it into a Sortable widget, make sure that the <ol> tag has an id of “sortable”.

<ol id="sortable">
  <%- @album.photos.each do |photo| -%>
    <li><%= photo.description %></li>
  <%- end -%>
</ol>

Then in the empty app/assets/javascripts/sortable.js, call the JQuery UI sortable function on the unordered list element.

$(function(){
  $('#sortable').sortable();
  $("#sortable").disableSelection();
})

Now, if you access the Edit Order page, the list items should be sortable by dragging and dropping the items.

Submit and persist the Sortable widget

The final step is to wrap a form around the sortable widget and submit the information. Let’s start by modifying the edit.html.erb view.

<%= form_for @album, :html => {id: "sortForm"} do |f| %>
   <ol id="sortable">
     <%- @album.photos.each do |photo| -%>
       <li class="ui-state-default" id="<%= photo.token %>"><%= photo.description %></li>
     <%- end -%>
   </ol>
   <input type="text" name="photo[tokens]" id="newOrder">
   <%= f.submit "Save" %>
 <%- end -%>

In the above code, we’ve added a form_for for the album and added some required markup on the form_for and the li tag. It is important to note that the id attribute of the li tag is the randomly generated token. This way, we don’t expose the database IDs of the photo to the users. Lastly, there is an text input field. This will be populated by the order of the photos before the form is submitted using the Javascript code presented below from the sortable.js file:

$(function(){
  $('#sortable').sortable();
  $('#sortable').disableSelection();
  $('#sortForm').on('submit', photoSorter.submit)
})

var photoSorter = {
  submit: function(e){
    $("#newOrder").val($("#sortable").sortable("toArray"))
  }
}

The photoSorter.submit function will take the ID attributes from the sortable elements and covert it into an array. Afterwards, it will populate the value of the text field with the array. Once the form is submitted, params should look similar to the following:

params = {
  "photo" => {"tokens"=>"YF-9earkp7vBrKSM3n5U5w,
                         PvljlQZBSC0UcUrg_DxBaw,
                         t8Pg_fR_j69SqKSiEjt4WQ"}
}

In the Album Controller, we need to make a few modifications. We first need to use strong params to sanitize the tokens and then on the update action, call Album#update_order and pass in the order params. We should also implement the Album#update_order method on the Album model:

# albums_controller.rb
...
  def update
    @album.update_order(order_params[:tokens])
    redirect_to @album
  end
...
  def order_params
    params.require(:photo).permit(:tokens)
  end
...

# album.rb
...
  def update_order(tokens)
    tokens.split(',').each.with_index(1) do |token, index|
      photo = self.photos.find_by(token: token)
      photo.order = index
      photo.save
    end
  end
...

The methods can be refactored some and abstracted out to a PhotoSorter class, but for the sake of this example, we’ll keep it as is. Now if you take a look at the album page, the photos have a new order in the Order column. As a final touch you can hide the text input field on the “Edit Photo Order” page so it is hidden from the users. The final product can be found in the “master” branch or the “3_persist_order”.

Another implementation of using the Sortable widget with a form can be achieved by using AJAX to send the information. We’ll save that for another blog post. Until next time happy coding!

Reference: http://stackoverflow.com/questions/5131460/using-jqueryui-sortable-list-with-forms