Adding a value-dependent data attribute to a simple_form checkbox collection
Asked Answered
A

4

8

I'm generating a list of checkboxes for a single collection like so:

= f.input :parts, as:check_boxes, collection: @parts_list

I want some checkboxes in the collection to disappear/reappear depending on the value of a select widget higher up in the form. (e.g. choosing "Tracker Robot" from the Robot select means that the "Legs" part checkbox disappears and the "Wheels" checkbox appears, etc.)

What I'd like to do is attach a computed data attribute to each individual Part checkbox, with the attribute value listing the Robots that can use that Part; then some JS will do the work of hiding/showing the checkboxes. However, I don't know how I can generate those data attributes using simple_form.

I would normally create a custom "parts" input, but there seems to be a problem with making custom collection inputs; it looks for a named method (collection_parts) inside form_builder.rb, which won't exist, and if I try and extend the FormBuilder it sends me down a major rabbit hole.

I could write some JS to load the data attrs into the generated HTML, but then I have to generate custom JS based on my Rails data, and that feels like the wrong way to do it.

Abiogenetic answered 8/2, 2013 at 20:37 Comment(0)
A
9

I think you can do it like this:

= f.input :parts do
  = f.collection_check_boxes :parts, @parts_list, :id, :to_s, item_wrapper_tag: :label, item_wrapper_class: :checkbox do |b|
    - b.check_box(data: { YOUR DATA ATTRIBUTES HERE }) + b.text
Algin answered 16/2, 2013 at 22:16 Comment(3)
But how can you do this dynamically? there's no variable to bind against…Wanids
b.object is the individual element of the collection, so you can do things like data: { identifier: b.object.identifier } etc., which will give each checkbox in the group its own data attribute (as opposite to the same value for all of them if you put it in the input_html)Algin
Firstly, sorry for the year-and-a-half delay in response! I like this answer best because it's so simple, even though there's still extra Ruby & JS code to write. I haven't tested it (I've long since moved on from both that project and the employer) but - assuming it works - putting the list of robots for each part in a data element makes a ton of sense and is easy to dynamically match against. Thank you!Abiogenetic
M
9

Let's assume that the form is for Order model and you are changing the parts collection based on the value of a field called region.

Update the form view. Specify the id for form, region field and parts field.

= simple_form_for(@order, :html => { :id => "order-form"}) do |f|
  = f.input :region, :wrapper_html => { :id => "order-form-region",                      |
      "data-parts-url" => parts_orders_path(:id => @order.id, :region => @order.region)} |

  = f.input :parts, as: check_boxes, collection: @parts_list,                            |
      :wrapper_html => { id' => 'parts-check-box-list'}                                  |

Add a new action called parts in the route.rb file.

resources :orders do
  collection do
    get :parts
  end
end

Add the new action to your controller

class OrdersController < ApplicationController

  # expects id and region as parameters
  def parts
    @order =  params[:id].present? ? Order.find(params[:id]) : Order.new
    @parts_list = Part.where(:region => params[:region])
  end
end

Add a helper

def parts_collection(order, parts_list)
  "".tap do |pc| 
    # to generate the markup for collection we need a dummy form
    simple_form_for(order) do |f| 
      pc << f.input(:parts, as: check_boxes, collection: parts_list, 
        :wrapper_html => {:id => 'parts-check-box-list'})
    end
  end
end

Add a js view for the action (orders/parts.js.erb)

$('#parts-check-box-list').replaceWith('<%= j(parts_collection(@order, @parts_list)) %>');

Register data change event handlers for region field in your application.js

$(document).ready(function() {
  $('#order-form').on("change", "#order-form-region", function () {
    // Access the data-parts-url set in the region field to submit JS request
    $.getScript($(this).attr('data-parts-url'));
  });
});
Morel answered 20/2, 2013 at 0:18 Comment(1)
Firstly, sorry for the year-and-a-half delay in response! This is such a complete answer, it's great. Thank you! Ultimately I went for Shuo Chen's answer - despite its incompleteness, it shows that I can get everything I need in the page in a really simple way, without using AJAX. But for anything more complex than my particular situation, yours looks like a great way to go and I'm sure I'll need it for something in the future. Thank you!Abiogenetic
F
1

this may be simpler.

Assumptions

@robots - an array containing the list of robots
@parts - a hash containing a list of parts for each robot

Sample Code

# controller
@robots = %w[tracker nontracker]
@parts = { tracker: %w[wheels lcd resistor], nontracker: %w[lcd resistor] }

# view
= f.input :robots, as: :select, collection: @robots, input_html: { id: 'robot-select' }

#parts-list

:javascript
  var parts = #{@parts.to_json};

  $(document).ready(function() {
    $('#robot-select').change(function() {
      $('#parts-list').html('');

      $(parts[$(this).val()]).each(function(index, text) {
        $('#parts-list').append('<input type="checkbox" value=' + text + '>' + text + '</input>')
      })          
    })
  })

you can see this working if you clone https://github.com/jvnill/simple_form_search_app and go to /robots

Fredelia answered 21/2, 2013 at 16:3 Comment(1)
Firstly, sorry for the year-and-a-half delay in response! I love how simple the code is for this one, it's tiny. The only thing I don't like is that it's building a chunk of the form on the client side, and I still need to give the checkboxes the right names for the parts. But thank you!Abiogenetic
W
0

Some input options in SimpleForm accept a lambda that gets called for every item in a collection:

f.input :role_ids, :collection => (1..10).to_a,
  :label_method => :to_i, :value_method => :to_i,
  :as => :check_boxes, :required=> true,
  :disabled => ->(item){ item.even? }

but input_html doesn't seem to be one of them.

The solution is probably to create a custom SimpleForm collection input that applies the data attributes itself. Not as flexible perhaps, but I think this is the only way to go for now.

There's a tutorial page on GitHub that should get you started.

Wanids answered 19/2, 2013 at 23:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.