22 Dec 14
Learn how to easily achieve a drag and drop sortable lists functionality in your rails app that updates via ajax
With me, is a simple to-do list application where users can create dummy to-do lists and displays them in card-like form just like in Trello. We want to enable users to sort the list by drag and drop so that they are displayed in the specific order the user sets them.
You can take a sneak peek at the final result
To achieve the drag and drop functionality, we will be using html5sortable jquery plugin. The reason I love this plugin as opposed to the widely used jQuery UI is its small size that means your page loads much faster while achieving the same functionality.
Grab the html5 sortable plugin form the
Github page. You can find the js in the src
directory. Add the html.sortable.js
file in your vendor/javascripts folder.
Next, lets require it in our application.js manifest file
Our Task model has two fields,
title
and description
. We need to store the position of each of our tasks and to do this we need to add a priority
column which will store the position of our task inside the database. We will be able to sort our tasks by using this field.
rails g migration addPriorityToTasks priority:integer
Run
rake db:migrate
to update our database.
Let's take a look at our task's index view
As you can see, its pretty standard and all it does is render the
_task.html.erb
partial. I have added a
.sortable
class to the div that houses our partial, It is this class that we will call our sortable plugin on so that it sorts our tasks.
Next up,all we have to do is make our task's partial sortable. In the tasks.js file, call sortable on our div with the sortable class
The sortable plugin will look for a
.sortable-placeholder
class to use to show a placeholder while drag and droping our tasks. You can style it as you wish to suit your case
With this we should now have a drag and drop functionality
Our tasks in the list can now be dragged and dropped into any position, but the new order isn’t persisted back to the database. When we reload our page, the tasks are shown back in their default position.
We will add a
data-id
attribute that will hold the id of the task and another attribute data-pos
that will hold the position of the task in the DOM. We will then send this attributes to our rails app each time the user has sorted the list and update the task's priority accordingly.
We then use jquery to set the
data-pos
on each of our tasks
When we inspect our DOM you can now see that jquery did set the data-pos for each of our tasks
Now that we have this in place, all we need is, after the user has finished sorting the tasks, we have to send the updated order back to rails. To do this, we will listen to the
sortupdate
callback function provided by the html5sortable plugin. This function is triggered every time the user has stopped sorting and the DOM position has changed. We will add logic to get the updated order inside this callback function.
You will notice in the ajax call, we submit a PUT request to the sort_tasks_path (tasks/sort). We don't have this url set up yet. In our
routes.rb
Next up, we will have to write a method in our TasksController that will store the updated position orders. The controller currently has the standard RESTful actions for listing, showing and creating tasks. We will add a method sort
to our tasks controller that will be responsible for storing the updated task order as specified by the user.
The sort method will loop through the order parameter and update the priority for that Task. Each parameter contains the Task id and its new position. We find the Task by its id then update the priority attribute. Since we don’t need to send anything back from the AJAX call so the method finally returns nothing.
We will arrange our tasks based on the priority column. In the task model, lets add a
default_scope
that orders our tasks based on the priority column in an ascending fashion.
And that's it!! Now when we drag the tasks about and then reload the page the items new position is persisted to the database. Hope this was helpful, have any questions or suggestions? feel free to use the comments sections below. You can also clone this app on my Github Repo.
Happy hacking!
Rails
20 Jun 18
In one of my recent projects, I was working on a scraper that needed to login into a website and download a file which I would then save to use later on. ...
Rails
05 Apr 15
Introduction It's been quite a while since my last tutorial and since then I've recieved alot of requests by email to implement a private messaging system ...
Ajax
22 Dec 14
With me, is a simple to-do list application where users can create dummy to-do lists and displays them in card-like form just like in Trello. We want to e...
Stefflan00
26 Dec 14
Do we have the chance to put any task on top? I mean we can put the first(top) task to an other place but we don't be able to put for example the third task to the top.
Joseph Ndungu mod
26 Dec 14
Thanks for pointing that out. I've found out adding "display: list-item;" to the ".sortable" css class does the trick. I've included this fix in the demo and in the tutorial. Thanks again! Cheers
Stefflan00
26 Dec 14
Thank you for this great tutorial. Looking forward reading more from you!
Joseph Ndungu mod
26 Dec 14
Your welcome Steff! I'll keep more tutorials coming.
Stefflan00
26 Dec 14
Ok just one more thing to add in .sortable => list-style-type: none; Otherwise we see a bullet before the first task.
Simon Mutie
15 Jan 15
Its great tutorial. Perfect skills.
Bob Walsh
26 Feb 15
Great tutorial and excellent code. One issue though in the repo. If you create 3 tasks, you cannot drag task 3 "in front of" task 1. It defaults to position 2. Not a big deal since you can then drag task 1 below task 3 (in second place).
benjamin mwendwa
23 Mar 15
Great tutorial Joseph.
scramlo
10 Apr 15
Just FYI for anyone who had an ExecJS error: I needed to convert the JS to CoffeeScript.
scramlo
10 Apr 15
My data-pos doesn't seem to be iterating through each li... do you know why this may be? JS:-
var ready, set_positions; set_positions = function(){ $('#move-me').each(function(i){ $(this).attr("data-pos",i+1); }); }
HTML:Christopher V Castillo
17 Aug 15
Thank you for such a good tutorial I want to create two columns and store the position of item dragged to column 2 or back to column one I was able to do it with this connectWith: '.connected' but i can't save it into my database! Do you have any ideas?
Sean Kelley
04 Oct 15
This works on development but does generate js error in console: Uncaught ReferenceError: module is not defined(anonymous function) @ html.sortable.self.js?body=1:433. When I push to heroku it stops working completely. Any ideas? I am using rails 4.2
Sean Kelley
04 Oct 15
FYI: this appears to be a bug => https://github.com/voidberg...
sri26
30 Oct 15
i was getting conflicts when apply the sortable to child level... i have applied sortable to parent level i.e to a div panel, and also i have applied to list item in the div while i was trying to sort the list items parent(div) and it is not applying to child level
sri26
30 Oct 15
i was nested sortable with this ajaxs sortable in rails 4
aakanksha0402
01 Aug 16
Loved the article. Its simple and straight forward even for a newbie like me. I faced a CSRF-Authentication error, however, once solved. I got exactly what i wanted. Thanx a ton
JCBoptimumweb
24 Jun 17
could you possibly shed some light on what caused your csrf auth error
Camilo Sad
05 Dec 16
Great article. Really helpful. Just one little thing that I had to change, using Rails 5 and turbolinks: Instead of - $(document).on('page:load', ready); I used - $(document).on('turbolinks:load', ready);
mehtasuraj09
07 Apr 17
Can you please explain how I can rearrange items in grid format. I want to create a rails app where I can rearrange stuff vertically as well as horizontally. Each horizontal row may have different number of items.