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.
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.
@Alexander Zubkov, thanks a lot for posting your module! I tried it out and it works! My preference though, is not for max+1 but max 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?
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.
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.
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:
This libarary is written using ARel from the ground-up. This leaves the code much cleaner than many implementations. ranked-model is also optimized to write to the database as little as possible: ranks are stored as a number between -8388607 and 8388607 (the MEDIUMINT range in MySQL). When an item is given a new position, it assigns itself a rank number between two neighbors. This allows several movements of items before no digits are available between two neighbors. When this occurs, ranked-model will try to shift other records out of the way. If items can't be easily shifted anymore, it will rebalance the distribution of rank numbers across all memebers of the ranked group.
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 syntaxrender :nothing => true.
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:
drafts.js.coffee
jQuery ->
$('#sortable').sortable()
_form.html.erb
# This partial is called by new and edit views from drafts_controller.rb
<ulid='sortable'><%@draft.clauses.each do |clause| %><%= content_tag_for :li, clause do%><%= link_to h(clause.name), clause_path(clause) %><%end%><%end%></ul>
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 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.
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.
"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.
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!
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
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?
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.
faqs_for_user = params[:faq].map{|x| x.to_i} & current_user.faq_id_list
faqs_for_user.each_with_index do |id, index|
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.
/app/controllers/sorts_faqs_controller.rb
classSortsFaqsController < ApplicationControllerdefcreate# same as FaqsController#sort shown in screencast endend
config/routes.rb
resource :sorts_faqs, only::create
app/views/faqs/index.html.erb
<ulid="faqs"data-update-url="<%= sorts_faqs_path %>"><%@faqs.each do |faq| %><!-- same as shown in screencast --><%end%></ul>
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:
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?
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+1butmax 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
whereclause. I'm too new to RoR to be able to figure this out. Any suggestions?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:
jQuery -> $('#sortable').sortable()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?
jQuery -> $('#download_types').sortable axis: 'y' handle: '.handle' update: -> $.put($(this).data('update-url'), $(this).sortable('serialize'))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
Im using Ruby 1.8.7 and Rails 3.1 and i was getting this error
WARNING: Can't verify CSRF token authenticitywhen 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"?
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!
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:
%tr.dynamic_field %td .field = f.label :assessment_id = f.text_field :assessment_id %td .field = f.label :milestone_id = f.text_field :milestone_id %td .field = f.label :sequence = f.text_field :sequenceThe 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?
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?
First sign in through GitHub to post a comment.