A few days ago I was able to add a nice functionality to a project. After adding many objects client asked me if it is possible to sort them manually on the administration page. The idea was to list objects in the particular order on the index page and to have an easy way to modify it. It could be a very simple task – just add field position in a model and text field in the form. Now imagine the client’s face when you say ‘You just need to update every record in your table’. It could be possible with a few, but with a bigger database it would be very time consuming. I decided to create something faster and more effective – drag-and-drop tables with position update.

Ranked Model

When I started my research I found many different solutions. The one I was really interested in was based on Ranked-model. It is a gem used for updating the row position in the table. When I move one object from one place to another, every object from collection gets updated. Nice and simple, but with one exception – if you are using Postgres it works only with Rails 4, so you can use it only when your project is up-to-date with the technology. Postgres was unsupported before Rails 4.0, but other database servers, for example MySQL work also with Rails 3. Additionally I added jQuery UI gem for making a table sortable.

gem 'ranked-model'
  gem 'jquery-rails'
  gem 'jquery-ui-rails'

Add new path for the sort action in the routes.rb file, and create the method in the controller. In the index add a method rank with the attribute used to sort. If you change the attribute value all other objects will also be updated, and ordered by it. What can be surprising is a subtraction of one when asigning new position. It is done just to compare position field values (started from 0) and position of the element in the table (started with 1). It is also dependent on the page number, so if you are not using pagintation the addition at the end of line is not necessary.

resources :users
  post 'users/sort' => 'users#sort' , as: :sort_users
 def index
    @users = User.rank(:position).paginate(page: params[:page], per_page: 20)
    @page = params[:page].nil? ? 1 : params[:page]
  end

  def sort
    @user = User.find(params[:id])
    @user.position = params[:position].to_i - 1 + (params[:page].to_i - 1) * 20
    respond_to do |format|
      if @user.save!
        format.json { head :ok }
      else
        format.json { head :error }
      end
    end
  end

The model has to include the gem and define the attribute to sort by. The gem by default can assign position values as negative. If you dont want it, add to the model a little function shown below. Now positions of elements in collection will always start from 0.

lass User < ActiveRecord::Base
  include RankedModel
  ranks :position
  after_save :update_position
  def update_position
    if self.position < 0
      self.position = 0
      save
    end
  end
end

Front side

The view of the table is very simple, looks mostly the same as table in typical index site. You just need to add ‘sortable’ id to table (don’t need to implement anything, it is included in jQuery-ui library). Additionally put the url to our sorting method from controller, and page parameter used for pagiation. In every row add a data parameter for item_id.

.tab-content
  #tab-pane
    - if !@users || @users.empty?
      %h3 No users
    - else
      %table.sortable{data: {update_url: '/users/sort', page: @page}}
        %tr
          %th Position
          %th Name
          %th Lastname
          %th Email
          %th Phone
          - @users.each do |user|
            = render 'user_row', user: user
      %p
        = will_paginate @users
%tr{data: {item_id: "#{user.id}"}, class: 'item'}
  %td= user.position
  %td= user.name
  %td= user.lastname
  %td= user.email
  %td= user.phone

To enable a drag-and-drop table, I added the jQuery function. Remember to include jQuery and jQuery-ui in application.js. The sorting function itself is very simple, it just takes item id and position and sends it to a given url. The controller method is not given directly here to make it more universal. I had to add sorting in many places, and I could do it with one function, by giving url to particular action.

//= require jquery
//= require jquery_ujs
//= require jquery-ui/sortable
//= require jquery-ui/effect-highlight
//= require sort
jQuery ->

  $('.sortable').sortable(
    axis: 'y'
    items: '.item'
    # highlight the row on drop to indicate an update
    stop: (e, ui) ->
      ui.item.children('td').effect('highlight', {}, 1000)
    update: (e, ui) ->
      my_url = $(this)[0].dataset.updateUrl
      item_id = ui.item[0].dataset.itemId
      page = $(this)[0].dataset.page
      position = ui.item.index()
      $.ajax(
        type: 'POST'
        url: my_url
        dataType: 'json'
        data: { id: item_id, position: position, page: page }
      )
  )

The effect is shown below. Every record in the table can be taken to a different position and the others will be updated. Giving this table on the admin page and adding order by position on the index site gives a nice functionality for organizing page content.

Conclusion

My solution for sortable table is finished. It can be changed a little bit, for example there is no need for using pagination unless you have a big table with data which is not comfortable for scrolling down 1000 pages. And one remark to pagination – you should also add text field in object edit form to type the position, because you will not be able to take and object from one page to another. And don’t worry – ranked-model will update other records after submiting the form. Summing up, this is just a simple scaffold, you can modify as you need. It is an example, which is easy to implement in your project and gives you a chance for customization. Try it!

Post tags:

Join our awesome team
Check offers

Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate