#147 Sortable Lists (revised)
Here I show how to use jQuery UI to make a sortable list and a "sort" action to handle the updating. Top it off with acts_as_list to make it feature complete.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
how it works with pagination?
I had the same question. But I think, I will have to show full list if want the users to sort through complete list.
Pagination is tricky because I'm not certain how you would sort across pages. You may want to do simple up/down arrow buttons that increment/decrement the position instead of drag + drop sorting.
Rather than using acts_as_list could we just give the position a default value? (i.e. 10000)
If that's the only feature you'd want from acts_as_list it seems a better solution.
You'll get problems when you add more then only one at a time
or just simple before_create to set max+1 on position
+1
I wrote a small module for that:
https://gist.github.com/1601872
@Alexander Zubkov, thanks a lot for posting your module! I tried it out and it works! My preference though, is not for
max+1
butmax of a particular set +1
. So I used your module as inspiration, but ended up doing this manually as follows.This works just as I want, but I wonder if there's a way to make this into a module! You'd have to be able to tell the callback method (set_position) which column (in this case "edition_id") to use in the
where
clause. I'm too new to RoR to be able to figure this out. Any suggestions?Thanks for that!
I set the position to Time.now.to_i as a default. You don't need to know a max position or reference the database to place it at the bottom of the list.
+1
Revised Railscasts are one of the best things ever. A nice idea taken from the Mythbusters ;]
Thanks for another great episode!
EDIT: It should also be under the Rails 3.1 tag since you were using coffeescript.
Glad you like the revised episodes. The 3.1 tag is for episodes where the main topic is a feature of 3.1 and cannot be applied to 3.0. It is true one would need to translate the CoffeeScript into JavaScript, but other than this the functionality is the same.
+1. Best $10 ever.
I agree. You are doing a great job Ryan!
Thank you Ryan,
Can we add to trash box to delete faq links :) with drag? Means Drag and Delete.
May be later, you will add this feature for your casts.
Regards
Sounds like a fun idea for a future episode, thanks for the suggestion.
Ryan you forgot to add the modified routes file to the show notes. Thanks for these!
Thanks Ryan! How would you use this with pagination?
I noticed this one before.. just want to point out that you shoud use $ as argument, not global variable
I am a little late to the party, but I would recommend looking at ranked model, it handles ordering a lot nicer than the (old) acts_as_list.
Here's a snippet from the Github project page:
I will definitively look into Ranked Model, looks really great! Thanks Jean!
I love that you snuck in "When will TextMate 2 be released?"
Very helpful screencast!
I think "render nothing: true" is a typo?
This is using the new hash syntax present in Ruby 1.9.2. If you are using Ruby 1.8.7 you would need to convert this to the old hash syntax
render :nothing => true
.Hi guys!
I'm having a little problem trying to understand 'content_tag_for', could you please give me a hint?
I'm not pretty sure how to get/use the IDs from inside a partial :( here is my code:
Thanks in advance! :D
Coda, I found this link:
http://railsforum.com/viewtopic.php?id=40499
so your >> here? <<
could be something like
not sure about the content tag and how you specify the class/id for CSS.
I'm using a fieldset as follows:
Good luck :)
The previous code works for new models, but you can get the existing id like this:
Wow, thank you VERY MUCH, tguevin!! I will absolutely give it a try! :)
I really appreciate your help.. you've made my day :P
That is a GREAT gravatar
Hey I'm using the same code to dynamically add fields too! How did you handle the order position for multiple new fields?
Hi, folks!
I'm having some issues here. I have Rails 3.1 and followed all the initial steps correctly.
The problem is that my "ul" list does not become "sortable". It does not work.
Here is my code:
Thanks in advance!
I am having the same problem. Were you able to solve it?
Unfortunately not!
When incorporating this code within my app, my list was not sorting until I added jquery-ui to my require list in the application.js manifest. Perhaps that is the same issue you are having?
What other considerations should be taken into account to use the PUT method, instead of POST? Is it as simple as changing the method name?
Short answer (via jquery.com) is: PUT and DELETE are not supported by all browsers.
It's more convenient to go with POST.
What if I want to use two models in the nested set view? An example would be a dynamic restaurant menu where I have menu_sections and menu_items and i want to drag menu items to sections? How do I tackle this?
Would have loved to see this working across different lists (moving items from one to the other). I already have something working for this, although I don't think it is very efficient.
I seem to be getting a "sort_faqs_url" not found error when trying to access my controller sort action. Everything matches the above code perfectly
How did you fix it ? I have the exact same problem.
Thanks
NVM
How did you guys fix this?
Does this work with mongoid?
No it doesn't but it's pretty easy to do without the 'acts_as_list' gem.
def sort_figures
@report = Report.find(params[:id])
params[:figures].each_with_index do |id, index|
@report.figures.find(id).update_all({position: index+1})
end
render nothing: true
end
I used this and it works also:
In this case, it uses update_attribute instead of update_all...update_all was giving me an error with Mongoid 2
Thank you.
With Rails 4 depreciating Relation#update_all, your solution worked.
+1
Im using Ruby 1.8.7 and Rails 3.1 and i was getting this error
WARNING: Can't verify CSRF token authenticity
when released the drag.So i google it and I had to do a modification of Coffee Script:
I hope to help someone else who has this problem!
Sorry! It's:
Thanks for the update. Can you show me what the routes look like with ruby 1.8.7? I can't seem to get mine working.
Nevermind, in my case it was:
Over a year later and it works. Thank you :)
thank you
Anyone know how efficient updating the sort order like this is? In the case of my application, users have hundreds of items in a list. If I have thousands of users are all changing the sort order like this on lists of hundreds or possibly thousands of items, it seems the efficiency would be bad.
What are some other thoughts on this?
Refer to "ranked model" in @Jean's comment above
Is the following change justifiable?
from:
to:
Ref: http://rhnh.net/2010/06/30/acts-as-list-will-break-in-production
"We use update_all here so that all of the updates are made in a single query"
hi Ryan, for me it still issues individual updates for each item in the list. that makes sense since the "update_all" call is inside of the each loop. am i missing something?
p.s. great work on the screencasts, keep 'em coming.
I'm wondering about this too. What is the "performance reason"?
You are correct, update_all is called once for each faq item, which results in a separate query for each faq item. The performance gain is because update_all updates the order field in a single query as opposed to first loading the model then doing an update in a separate query.
This still works in Rails 4 by the way, but the syntax is a little different:
It is actually possible to do all updates in a single query, preferably using the fact that your model is using acts_as_list:
This makes use of the fact that the insert_at method already has the logic to optimally execute the move in the most efficient fashion. For me this is more than an order of magnitude faster since I was using sorted on a list that could contain upwards of 500 items.
Took too long to edit to correct my mistake. The code in the form above only works when the moved item is moved up. This is the hastily corrected version which is in need of some refactoring:
What would be the best way to store the position with double digits ; set 01 instead of 1, 02 instead of 2 and so on until 10.
Thanks for another great one Ryan!
Found it and can't delete my post. Sorry
I used this - "value: "%02d" % (index+1).to_s"
Ryan, Thanks a million (yet again) I've used this method in a few apps it really adds a useful and dynamic feature.
I'm using it in one case to allow users to sort names. It's working fine but I'd like to add a bit more to it in that I'd like to style the Number 1 name differently. This is easy for me to do on page refresh but it'd be smashing if the Name in question got styled dynamically as it was dragged to the top of the list by the user i.e. without the user pressing F5 or CMD+R, depending on your religon!
Screencast is awesome.
I'm running into an issue: the sortable order gets messed up if the users page does not match the database. This can happen if they are using an outdated view (didn't refresh page) or another user changed the order before they refreshed the page. Any idea how to resolve this? Perform a check somehow to see if the data matches the db before the sort, else redirect.?
I'm having trouble with a slightly more advanced version of this where I'm trying to use connected sortable lists and sort things between lists. I have a few models (Page, Row, Column, Block) and want to let users drag blocks between columns. The tricky part that I can't figure out is the best way to delete a column/row if it no longer has blocks after sorting. Can anyone help? I posted the question in more detail on stackoverflow here http://stackoverflow.com/questions/10500994/rails-sort-connected-lists-and-delete-empty-parent
Hey, thanks for the video. How would this work if the set of items is based on a has_many through model?
for example,
I have a list of pieces like this: @queue.pieces
pieces and queues are related via a has_many through piece_queues
would that work?
I think i got it working but i'm trying to use a table rather than a ul/li and it doesn't seem to be passing the right thing to the sort action. no matter what, it passes the original order even after dragging+dropping to reorder the list. is there a way to get tables to work with this?
I got it to work with tables by using this structure:
So basically you wrap your headers into a %thead tag and your other rows with a %tbody tag.
Thanks for the tbody on the sortable call, was having an issue where it would drag the entire table!
I know it's 2 years later, but thank you for this! Huge help just now!
Anyone done anything with this & update_all where the items being sorted have a user_id parent. As it is, a Post with a random set of Faq's could resort regardless of who the parent user_id is.
I have sort of got it.
The faq_id_list returns a list of id's that are allowed to be sorted. The procedure relies on the fact that the array intersection function returns items in the same order as the first parameter. (gulp)
If anyone has anything better here, would be good to see it.
One thing I would do different is move the sorting into a new controller class and put the sorting in its #create action. This way we can stay within the boundaries of the seven actions.
Or going even further, move all that database stuff into the model.
Is there a way to do this when you have nested forms (associations)? As in, we have a @license object, and every @license has @progress_point. Well, the form for licenses looks like this:
The partial "progress_point_fields" has:
The problem is that :progress_points isn't an @object so I can't exactly get the ID (or at least I'm not aware of any way of getting it).
Any ideas? Thanks in advance
Nevermind, the thread above that started by "Coda" answered this.
Anyone know how I might update the position number in the view when the user drops the element? Right now, everything is working but the numbers I have showing do not update to show their new position. It's probably basic ajax stuff, any ideas?
+1
Just a heads up:
After installing gem, and adding 'acts_as_list' to your model you might get this error...
..if you don't restart the server.
Anyone have an example of how would you write a spec or test something like this?
+1
Routing Error, help :)
No route matches {:action=>"sort", :controller=>"objectives", :profile_name=>"Codemaster"}
I did everything else as stated in the video.
data-update-url="<%= sort_task_objectives_url %>"
I do have
As you are using nested resources you need to pass the task.
data-update-url="<%= sort_task_objectives_url(@task) %>"
Thanks I was just about to post my newby mistake. :)
Added both (@user, task) since I was indexing associations in the parent object's index (aka "task")
I'm running into a strange issue: when my initial page loads, the list is NOT sortable. If I refresh, it becomes sortable and everything works.
I'm using ZURB Foundation in my app. Could there be some sort of conflict?
I'm also pretty fuzzy on javascript, jQuery and the function() call.
Could it be that the list is shown before the document finishes loading? Isn't this solved by the function() call? Why doesn't Ryan have that function() call in his coffee script?
Thanks!
Hey Mihai - I'm having the same issue (but I'm using Bootstrap, not ZURB). Did you ever figure out what the problem was?
Just solved this - it's a problem between turbolinks and jQuery UI. Installed this gem, problem disappeared.
https://github.com/kossnocorp/jquery.turbolinks
Rails 4
I used Daren Serra Dias Warburton's solution to get this working in Rails 4.
undefined method `sortable_element'
I'm getting this error in Rails-4.
This method is deprecated in rails-4 then how did you worked with it ?
Rails 4
Thank you very much!
Thanks so much! Bashing head against syntax then saw your comment.
Life saved.
Thank you for sharing
undefined method `sortable_element'
I'm getting this error in Rails-4.
This method is deprecated in rails-4 then how did you worked with it ?
I'm using rails 4 & i'm getting this error
"Couldn't find HomeworkType without an ID"
showing as 404 error while dragging & dropping.
Please help me guys
You may want to ask this on Stackoverflow. Then you can provide some more details about your setup.
(If you haven't figured it out already :)
I'm trying to implement sortable rows in my Rails 4 application. For some reason dragging a row doesn't save the position in the database. I'm not sure whether the sort action method gets called.
When I check the Javascript console with Google developer tools no Javascript errors are thrown, so I'm not sure what's the problem.
My code is at:
https://github.com/acandael/beautysalonapp2/tree/drag-and-drop
anyone else got issues implement this drag and drop functionality for a Rails 4 application?
thanks for your help,
Anthony
when I tail the development log, I see this error message:
Started POST "/admin/sections" for 127.0.0.1 at 2014-11-13 20:49:51 +0100
Processing by Admin::SectionsController#create as /
Parameters: {"section"=>["3", "4", "6", "2", "5"]}
Completed 500 Internal Server Error in 1ms
NoMethodError (undefined method
permit' for ["3", "4", "6", "2", "5"]:Array):
section_params'app/controllers/admin/sections_controller.rb:54:in
app/controllers/admin/sections_controller.rb:15:in `create'
You were very close to get it working. The cause of this failure is strong parameters. You have to do:
params.require(:your_model).permit!
You can also specify which fields you want to allow by doing:
In your ascii cast I believe there is a typo
jQuery ->;
$('#faqs').sortable();
should be
jQuery ->
$('#faqs').sortable();
There is an extra semicolon that was breaking my code.
Read how to get Sortable Lists to work with Rails 5
The content_tag_for was available in Rails 3 and 4. It has been removed from Rails 5.
To bypass content_tag_for try this simple format - it works for me with Ruby 5: