3

I have a form with this collection_select

    <%= collection_select :bmp, :bmpsublist_id,
                          Bmpsublist.where(:bmplist_id => @bmp.bmp_id), :id,
                          :name,{ :required => false, 
                          :selected => @bmp.bmpsublist_id, } %>

I would like to be able to get the value of this collection_select so that lower down in the same form, I can check to see which list I should use when displaying another collection_select

Something like this partial pseudocode here:

if earlier result == 2 then
  use this list: Irrigation.where(:id != 8)
else
  use this other list: Irrigation.all

and they would be updating the collection_select:

<%= collection_select :bmp, :irrigation_id, the_chosen_list_from_above, :id, :name, 
                            {:prompt => 'Select Irrigation Type'}, {:required => true} %>

How can I do that?

Santi Gallego
  • 202
  • 1
  • 18

2 Answers2

2

Based on what you've asked, there are two ways to query and apply the value of the collection: static and dynamic.

Static occurs at the time that the ERB view is rendered, and this will apply the logic at the time that the page is initially rendered and loaded. Dynamic occurs after the page is loaded, and as the user interacts with the elements on the page. Which approach you choose to go with depends entirely on your application's design and intended level of interaction with the user.

Static Detection

You're already specifying the selected item in the initial collection_select, so you can reuse that in your later code. Try this, based on your pseudocode example:

<% if @bmp.bmpsublist_id == 2 %>
  <% irrigation_list = ["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking"] %>
<% else %>
  <% irrigation_list = ["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking", "Pads and Pipes - Tailwater Irrigation"] %>
<% end %>
<%= select :bmp, :irrigation_id, options_for_select(irrigation_list),
           { :prompt => 'Select Irrigation Type'}, { :required => true } %>

Why will this work? The :selected option for the initial collection_select is where you provide which option will be initially chosen. Since this value is typically taken from the model value, it's supplied in a separate param from the actual collection values. So, it's queued up and ready for you, simply by virtue of sticking to the Rails conventions.

The subsequent select builds the HTML <select> element and uses the options_for_select to turn the array of options into HTML <option> elements. This way, you can use the variable list of options to select from, based on which element from the original collection_select was chosen.

Best thing of all: with the static approach, you don't have to drop into Javascript (or jQuery) to do this; it gets rendered directly by the ERB template (or the HAML template, if that's your bag).

Dynamic Detection

If you actually wanted dynamic behavior, you can drop into Javascript / jQuery and get it done. You can create your "Irrigation Types" select just like with the static approach (above), except that you initialize it with all of the options, like this:

<%= select :bmp, :irrigation_id, 
           options_for_select(["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking", "Pads and Pipes - Tailwater Irrigation"]),
           { :prompt => 'Select Irrigation Type'}, { :required => true } %>

Then, edit the Javascript source associated with your view (let's call it Product). Open the app/assets/javascripts/product.js (if you use CoffeeScript, it's the product.coffee file in the same directory).

Edit that Javascript file to include this code:

function OnProductEditForm() {
    // Edit the selectors to match the actual generated "id" for the collections
    var bmp_collection = $("#product_bmp");
    var drip_collection = $("#product_irrigation_type");
    var drip_option = drip_collection.find("option")[2];

    function select_available_drip_options() {
        var value = bmp_collection.val();

        if (value == 2) {
            drip_option.attr("disabled", "disabled");
        } else {
            drip_option.removeAttr("disabled");
        }
    }

    bmp_collection.change(function() {
       select_available_drip_options();
    });

    select_available_drip_options();
}

This identifies the HTML element of the collection and installs a change event handler. You'll need to verify the collection element's id, as per the code comment, and the rest happens from there. When the collection is changed (a new value is chosen), the event handler will hide or show the third select <option> (specified as find("option")[2]), as appropriate for the #product_bmp selection.

Next, in the app/views/products/_form.html.erb, include this at the end of the file:

<script>
    jQuery(document).ready(OnProductEditForm);
    // Uncomment the next 2 lines for TurboLinks page refreshing
    //jQuery(document).on('page:load', OnProductEditForm);
    //jQuery(document).on('page:restore', OnProductEditForm);
</script>

This will automatically load the OnProductEditForm method when the page loads, and will result in the afore-mentioned event handler getting installed. Note that the last 2 lines are necessary if you have TurboLinks enabled, as TurboLinks initiates events for page loading independently of the standard $(document).ready.

And that's all there is to it. Adding dynamic behavior is just that easy!

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
  • I'm following you until the `use_this_list();` and `use_this_other_list();` methods. Would I just be say creating two different arrays in those methods? Also can I just access them in the form, or do I have to somehow pass them? Sorry if I'm missing something obvious, I'm kind of new to this stuff – Santi Gallego Jul 01 '16 at 14:55
  • I'm afraid that I don't know what you should be doing there; you didn't include that information in your question. All I know is that you said that you want to determine "which list I should use when displaying another `collection_select`", and then you used the pseudocode "use this list" and "use this other list". That's all the information that I have. I don't even know what these lists are or what you intend to do with them. If you update your question to include all of the details, I can update my answer. – Michael Gaskill Jul 01 '16 at 16:11
  • I have updated my question. Basically I just want to exclude one of the options if the bmp selected is 2 – Santi Gallego Jul 01 '16 at 16:25
  • OK, I have updated my answer to include those details. – Michael Gaskill Jul 01 '16 at 16:42
  • Does the list have to be like this? `["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking", "Pads and Pipes - Tailwater Irrigation"]` because that gives me an error `undefined method 'name' for "Sprinkle":String`. Also on the `var drip_option = drip_collection.find("option")[2];` is `option` an id and is the `2` the id of the value that is going to be removed? – Santi Gallego Jul 01 '16 at 16:58
  • @SantiGallego I updated my answer to include the information. The second select now uses the `select` and `options_for_select` API methods, which allows the select element to be built from an array of options - `collection_for_select` was not the right choice for this particular select element, because of the array instead of a collection of model elements. The `find("option")[2]` magic says to search for the 3rd (zero-based) ` – Michael Gaskill Jul 04 '16 at 07:07
  • I was actually not able to get it working but decided to move on to more important things as the this issue wasn't an imperative issue – Santi Gallego Jul 14 '16 at 15:55
0

You're gonna have to use some javascript (jquery and ajax, I would suggest). When the value of the first select change (jquery), it requests (ajax) the collection (passing the current value selected) to a controller action that returns the collection that should be used. With the collection returned, you populate the options (jquery) for the second select. It's not quite simple, but if you ever did something like that, you shouldn't have problems. If never did, do some research about it... it's quite useful and improve user experience a lot!

Ronan Lopes
  • 3,320
  • 4
  • 25
  • 51