Skip to main content

video about sortable lists

Rails • Asked by Anthony Candaele

Hi,

I'm not sure if this is the right place to post this issue, but I'm looking for a way in my Rails app to reorder items by drag and dropping.

I tried to implement the solution by Ryan Bates in this Railscast:

http://railscasts.com/episodes/147-sortable-lists-revised?view=comments

but it doesn't seem to work in Rails 4.

I am able to drag and drop the rows in my table, but the reordering does not get persisted to the database. When I tail the production log, I'm seeing this server error:

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):
app/controllers/admin/sections_controller.rb:54:insection_params'
app/controllers/admin/sections_controller.rb:15:in `create'

Because reordering items in a Rails app is a common requirement, I would love to see a screencast about this on Gorails, it would benefit a lot of my Rails applications, and as a imagine, a lot of other developers applications.

greetings,

Anthony


Oh that's a good point. It's not formatted properly for strong_params, so you'd have trouble with it accepting things. I'll definitely plan on covering an update to that to how to do it formatted a little more standardized for strong_params.

In the meantime, you should be able to change your require & permit to just this:

def order_params
  params.permit(section: [])
end

Note that you'll only want to use this on the sort action and not the rest of the regular crud actions.


Hi Chris,

I'm looking forward to that screencast : )

In the meantime I try your temporary fix.

greetings,

Anthony


Hi Chris,

How do I call this order_params method on the sort action?

Currently the sort method looks like this:

def sort
params[:section].each_with_index do |id, index|
Section.update_all({position: index+1}, {id: id})
end
render nothing: true
end


Oh duh. You probably don't need strong_params for this. Your original logs were calling section_params so I was thinking about fixing that.

Your sort method should work fine like that unless the update_all query needs to get different parameters in the new ActiveRecord versions but you should be good.


I tried it with this sort method:

  def sort
    params[:section].each_with_index do |id, index|
     Section.where(id: id).update_all({position: index+1})
    end
    render nothing: true
  end

But when I drag a section item, I'm seeing this error in the server log:

Started POST "/admin/sections" for 127.0.0.1 at 2014-12-19 14:26:28 +0100

Processing by Admin::SectionsController#create as /
Parameters: {"section"=>["2", "1", "4", "5", "3"]}
Completed 500 Internal Server Error in 1ms

NoMethodError (undefined method permit' for ["2", "1", "4", "5", "3"]:Array):
app/controllers/admin/sections_controller.rb:64:in
section_params'
app/controllers/admin/sections_controller.rb:20:in `create'

I tried to fix this by adding the sections array to the permit method:

  params.require(:section).permit(:title, :description, :image, :remove_image, :category_id, { :section => [] })

But that didn't fix it either.

the source code for my Admin::SectionsController is here:

https://github.com/acandael/beautysalonapp2/blob/sortable_list/app/controllers/admin/sections_controller.rb


Ah ha! That's sending to your create action, but you have a sort action for sorting. You'll need to update your JS to point to the sort sections route.


I have this route for the sort action:

resources :sections do
  collection { post :sort }
end

my Javascript (Coffeescript-file) looks like this:

app/assets/javascripts/sections.js.coffee

jQuery ->
  $('#sections tbody').sortable
    axis: 'y'
    update: ->
      $.post($(this).data('update_url'), $(this).sortable('serialize'))

The route and coffeescript are literally taken from Ryan Bates' Railscast: http://railscasts.com/episodes/147-sortable-lists-revised


Does your update_url data attribute point to /sections/sort?


I guess so, I implement this in my view like this:

...
<tbody data-update-url="<%= sort_admin_sections_url %>">
...

the source for my view can be found here: https://github.com/acandael/beautysalonapp2/blob/sortable_list/app/views/admin/sections/index.html.erb

I tested it in my Coffee script with a simple alert, an when I drag and drop a section item, the alert() gets triggered:

jQuery ->
  $('#sections tbody').sortable
    axis: 'y'
    update: ->
      alert("I was drag and dropped");

That seems right, but it sounds like your JS is making a POST to the create action. Which means your sort action isn't being called.

Basically this Started POST "/admin/sections" for 127.0.0.1 at 2014-12-19 14:26:28 +0100
Should be Started POST "/admin/sections/sort" for 127.0.0.1 at 2014-12-19 14:26:28 +0100

And there is something causing your JS to POST to the wrong URL.

I think your JS isn't grabbing the update-url attribute because your attribute doesn't match. You've got $(this).data('update_url') in the JS but data-update-url in the view. Data attributes always use dashes, so I'm guessing that this returns a null and it POSTs to the current url which is /admin/sections.

Change that to $(this).data('update-url') and I think you'll be alright. Does that make sense?


hurray, it's working!!

thanks a lot. This added a much needed piece of functionality to my Rails app.

you made my weekend Chris : )

Anthony


there's just one more thing I need to solve.

I've noticed that I can only drag items if I manually refresh the page.

So when I visit the page the first time I can not drag items. Only after clicking the browser refresh button, I can drag items.

Maybe this has something to do with the way the script is loaded, or the way the jQuery function is called.



Interesting regarding the refresh problem. Your code seems fine, but are there any Javascript errors on the page the first time you load it?


no, there are no Javascipt errors in the Chrome Dev Tools console.

However, I have another jQuery related issue. I'm using the matchHeight.js plugin to set equal heights for elements, and here I also notice that sometimes the matchHeight() function only gets called when the page is manually refreshed with the browser refresh button.

So now I start to wonder if those issues could be interrelated somehow.


Oh interesting, it definitely could be then. I haven't seen that lib before but that's cool. I like that it works across rows. Stupid that CSS can't do it easily!


Hi Chris,

I found the culprit: Turbolinks!

I found the issue by this post on StackOverflow:

http://stackoverflow.com/questions/17696570/jquery-ui-autocomplete-doesnt-work-when-going-to-page-via-page-navigation

...
"The problem was with Rails 4 turbolinks which are enabled by default. When you use them you can not longer lay on jquery.ready() function from js to fire on page load, so you need to use page:load instead or you can install jquery.turbolinks gem which will bind page:load to query.ready()."
...

then I installed this jquery-turbolinks gem and everything is now working as expected.

greetings, and oh by the way, congrats on your merger with OneMonth,

Anthony


Doh! That would make a LOT of sense. I need to learn more about how to write my jQuery better for turbolinks support but I always notice things that it caches weird navigation items.

And thanks! We've got a lot of great plans to just keep improving things. :)


Anyone know about a good gem for listing stuff in a nice way? I've been looking at at gem called SmartListing(http://showcase.sology.eu/smart_listing) but looking for a few alternatives.


I've always used acts_as_list and been really happy with it. https://github.com/swanandp/acts_as_list


Cool, I would love to see a video on acts_as_list


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 24,647+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.