← Back to Upcase

Drag and drop functionality for admin panel


(Acandael Acandael) #1

Hi,

For my latest Rails project (a website for a cosmetologist), I have created some content management functionality:

Now I would like to implement drag and drop functionality, so an administrator kan reorder items (treatments) by draging an dropping.

Has anyone implemented drag and drop functionality in a Rails application? What tools did you use?

thanks for the advice,

Anthony


(Acandael Acandael) #2

I found this screencast by Ryan Bates:

147 Sortable Lists (Revisited)

The screencast is a bit dated, it dates from 2011 and was made with Rails 3.1

I followed it along, and I’m able to drag and drop the table rows of my admin panel, but the changes don’t get persisted to the database.

My view template looks like this:

app/views/admin/sections/index.html.erb

<section class="dashboard">
  <%= button_to "Sectie Toevoegen", new_admin_section_path, method: "get" %>
  <table id="sections" class="table-borders">
    <tbody data-update-url="<%= sort_admin_sections_url %>">
    <% @sections.each do |section| %>
      <%= content_tag_for :tr, section do %>
        <td><%= link_to section.title, admin_section_path(section) %></td>
        <td><%= link_to "Aanpassen", edit_admin_section_path(section) %>&nbsp;&nbsp;<%= link_to 'Verwijderen', admin_section_path(section), method: :delete, data: { confirm: "Ben je zeker?" } %></td>
        <% end %>
    <% end %>
    </tbody>
  </table>
</section>

The coffeescript file with that handles the Javascript looks like this:

app/assets/javascripts/sections.js.coffee


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

I tested the ‘update’ callback with a simple alert() to check if the update callback gets triggered when a table row is dragged and dropped, and that worked

On my controller, the sort() method which handles persistance to the database of a changed position, looks like this:

app/controllers/admin/sections_controller.rb



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

I think the problem must be located in the sort action, when I tail my development.log, I see this 500 server error, when dragging a table row:

Started POST "/admin/sections" for 127.0.0.1 at 2014-11-13 21:40:46 +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:in `section_params'
  app/controllers/admin/sections_controller.rb:15:in `create'

Finaly my sort route looks like this:

namespace :admin do
    ...
    resources :sections do
      collection { post :sort }
    end
end

I guess the problem must be caused by the new mass assignment security features in Rails4, but I’m not sure how to fix it.

it tried to add ‘:position => []’ to my private security_params method:

private

  def section_params
    params.require(:section).permit(:title, :description, :image, :category_id, :position => [])
  end

But that didn’t fix it.

Any help is much appreciated, my repository is at:

https://github.com/acandael/beautysalonapp2/tree/drag-and-drop

greetings,

Anthony


(Pedro Moreira) #3

What does your params hash look like? It seems that there is no key for your permission array.


(Acandael Acandael) #4

hmmm, it seems like the sort() action method doesn’t get called when a row is dragged.

I tried to raise an error in the sort method to check on the params[:section] hash:

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

but nothing gets raised. Maybe there is an issue with the sort_admin_sections_url path, but when I check the url als it is rendered, it looks ok:

<tbody data-update-url="http://localhost:3000/admin/sections/sort">

so for some reason, the update callback in the coffeescript file doesn’t invoke the sort() action method:

update: ->
  $.post($(this).data('update_url'), $(this).sortable('serialize'))

but I don’t understand, then why do I see this server 500 error message in development.log:

Started POST "/admin/sections" for 127.0.0.1 at 2014-11-14 14:37:24 +0100
Processing by Admin::SectionsController#create as */*
  Parameters: {"section"=>["3", "6", "4", "2", "5"]}
Completed 500 Internal Server Error in 1ms

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

(Acandael Acandael) #5

I’m pretty sure this NoMethodError (undefined method ‘permit’ is the key.

I tried several things to fix it:

def section_params
    params.require(:section).permit(:title, :description, :image, :category_id, :position => [])
end

def section_params
   params.require(:section).permit(:title, :description, :image, :category_id, :section => [])
end

but nothing works, also, I not sure why the sections#create method gets called when I dragg a row

I tried to follow Pedro’s suggestion, and look what’s in the params[:section] hash, but I can’t find a way to check the params[:section] hash at runtime. I tried to check by adding a raise call in the sections#sort method:

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

but that doesn’t do anything.

any help is greatly appreciated

Anthony


(Acandael Acandael) #6

found the issue, with the help of Chris Oliver from Gorails.

The error was realy stupid - as usual - , in my sections.js.coffee script I wrongly defined the update-url data element:

app/assets/javascripts/sections.js.coffee

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

should have been:

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

everything is working fine now :smile: