Mel Mel - 3 months ago 25
jQuery Question

Rails 4 - dynamically populate 2nd select menu based on choice in first select menu in a nested form

I have models for Project and Ethic in my Rails 4 app.

The ethics view has a nested fields form (using simple form simple fields for) contained in it. The ethic form fields are nested in the projects form.

The ethic form fields has 2 select menus. The first menu offers a set of options for a category. The 2nd select option is a list of subcategories.

I'm trying to figure out how to populate the 2nd select menu with the right options, based on the choice made in the 1st select menu.

In my project.js file, I have:

jQuery(document).ready(function() {

jQuery("#project_ethic_attributes.main_category").change(function() {
var category = $("#project_ethic_attributes.main_category").val(),
sub_category = $("#project_ethic_attributes.sub_category"),
options = [],
str = "";
sub_category.find('option').remove();
if(category == 'Risk of harm'){
options = ["Physical Harm", "Psychological distress or discomfort", "Social disadvantage", "Harm to participants", "Financial status", "Privacy"]
}

});

// jQuery(".main_category").change(function() {
// var category = $(".main_category").val(),
// sub_category = $(".sub_category"),
// options = [],
// str = "";
// sub_category.find('option').remove();

// if(category == 'Risk of harm'){
// options = ["Physical Harm", "Psychological distress or discomfort", "Social disadvantage", "Harm to participants", "Financial status", "Privacy"]
// }
// else if(category == 'Informed consent'){
// options = ["Explanation of research", "Explanation of participant's role in research"]
// }
// else if(category == 'Anonymity and Confidentiality'){
// options = ["Remove identifiers", "Use proxies", "Disclosure for limited purposes"]
// }
// else if(category == 'Deceptive practices'){
// options = ["Feasibility"]
// }
// else if(category == 'Right to withdraw'){
// options = ["Right to withdraw from participation in the project"]
// }
// if(options.length > 0){
// for(i=0;i<options.length;i++){
// str = '<option value="' + options[i] + '">' + options[i] + '</option>'
// sub_category.append(str);
// }
// sub_category.val(options[0]);
// }


});


I can't figure out what Im doing wrong. Regardless of the choice I make in the 1st option, the 2nd select menu is populated with options that belong to the last category.

My projects form has:

<%= f.simple_fields_for :ethics do |f| %>
<%= render 'ethics/ethic_fields', f: f %>
<% end %>
<%= link_to_add_association 'Add an ethics consideration', f, :ethics, partial: 'ethics/ethic_fields' %>


My ethic form fields has:

<%= f.input :category, collection: [ "Risk of harm", "Informed consent", "Anonymity and Confidentiality", "Deceptive practices", "Right to withdraw"], :label => "Principle", prompt: 'select', id: "main_category" %>


<%= f.input :subcategory, collection: text_for_subcategory(@category), :label => "Subcategory", prompt: 'select', id: "sub_category" %>


My ethic view helper has:

def text_for_subcategory(category)
if category == 'Risk of harm'
[ "Physical Harm", "Psychological distress or discomfort", "Social disadvantage", "Harm to participants", "Financial status", "Privacy"]
elsif category == 'Informed consent'
["Explanation of research", "Explanation of participant's role in research"]
elsif category == 'Anonymity and Confidentiality'
["Remove identifiers", "Use proxies", "Disclosure for limited purposes"]
elsif category == 'Deceptive practices'
["Feasibility"]
else category == 'Right to withdraw'
["Right to withdraw from participation in the project"]
end
end


Can anyone see what i need to do to populate the second select menu with the right options based on the choice made in the 1st select menu. I'm wondering if I'm not supposed to write the jQuery in the project.js file, given the form fields are contained within an ethic view partial (rendered in the projects form).

MIXAN'S SUGGESTION

My form now has:

<%= f.input :category, collection: [ "Risk of harm", "Informed consent", "Anonymity and Confidentiality", "Deceptive practices", "Right to withdraw"], option:'disabled selected value', :label => "Principle", prompt: 'select', id: "main_category" %>


<%= f.input :subcategory, :label => "Subcategory", prompt: 'select', id: "sub_category", option:'disabled' %>


I had to change the form a bit because I use simple form with rails. I'm not sure if I have added the 'disabled selected value' and 'disabled' options to the form inputs correctly.

At the moment, the form renders- but the second menu is not populated with any content. I wonder if that has something to do with the use of the option tags in the js file?

ethics.js has:

jQuery(document).ready(function() {
var optionsMap = {
'Risk of harm': [
'Physical Harm',
'Psychological distress or discomfort',
'Social disadvantage',
'Harm to participants',
'Financial status',
'Privacy'
],
'Informed consent': [
'Explanation of research',
"Explanation of participant's role in research"
],
'Anonymity and Confidentiality': [
'Remove identifiers', 'Use proxies', 'Disclosure for limited purposes'
],
'Deceptive practices': [
'Feasibility'
],
'Right to withdraw': [
'Right to withdraw from participation in the project'
]
};

jQuery('#main_category').change(function() {
var category = jQuery(this).val(),
$subCategory = jQuery('#sub_category'),
newOptions = optionsMap[category];
$subCategory.attr('disabled', false)
$subCategory.empty();
$.each(newOptions, function() {
$subCategory.append(jQuery("<option></option>").text(this));
});
})
});

Answer

In provided example seems that you selecting wrong DOM elements. Just reference them by id, it will be more clear and it will be not so coupled with attributes naming. I suggesting the next approach for your task. First build the map of selects on client side:

var optionsMap = {
    'Risk of harm': [
      'Physical Harm', 
      'Psychological distress or discomfort', 
      'Social disadvantage', 
      'Harm to participants', 
      'Financial status', 
      'Privacy'
    ],
    'Informed consent': [
      'Explanation of research', 
      "Explanation of participant's role in research"
    ],
    'Anonymity and Confidentiality': [
      'Remove identifiers', 'Use proxies', 'Disclosure for limited purposes'
    ],
    'Deceptive practices': [
      'Feasibility'
    ],
    'Right to withdraw': [
      'Right to withdraw from participation in the project'
    ]
  };

Then just listen for changes of main select and rebuild options for sub select.

jQuery('#main_category').change(function() {
  var category = jQuery(this).val(),
      $subCategory = jQuery('#sub_category'),
      newOptions = optionsMap[category];

  $subCategory.empty();
  $.each(newOptions, function(key,value) {
    $subCategory.append(jQuery("<option></option>").attr("value", value).text(key));
  });
})

Here an example how it looks like with plain html form

jQuery(document).ready(function() {
  var optionsMap = {
      'Risk of harm': [
        'Physical Harm', 
        'Psychological distress or discomfort', 
        'Social disadvantage', 
        'Harm to participants', 
        'Financial status', 
        'Privacy'
      ],
      'Informed consent': [
        'Explanation of research', 
        "Explanation of participant's role in research"
      ],
      'Anonymity and Confidentiality': [
        'Remove identifiers', 'Use proxies', 'Disclosure for limited purposes'
      ],
      'Deceptive practices': [
        'Feasibility'
      ],
      'Right to withdraw': [
        'Right to withdraw from participation in the project'
      ]
    };

  jQuery('#main_category').change(function() {
    var category = jQuery(this).val(),
        $subCategory = jQuery('#sub_category'),
        newOptions = optionsMap[category];
    $subCategory.attr('disabled', false)
    $subCategory.empty();
    $.each(newOptions, function() {
      $subCategory.append(jQuery("<option></option>").text(this));
    });
  })
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
  <select id='main_category'>
    <option disabled selected value> -- select an option -- </option>
    <option>Risk of harm</option>
    <option>Informed consent</option>
    <option>Anonymity and Confidentiality</option>
    <option>Deceptive practices</option>
    <option>Right to withdraw</option>
  </select>
  <select id='sub_category' disabled>
  </select>
</form>

Comments