3
votes

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));
    });
  })
});

MIXAN'S AMENDED SUGGESTION

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


<%= f.select :subcategory, [], {}, id: "sub_category", disabled: true %>

When I try this, the formatting of the menu drop down for the first category is changed, but the functionality is the same as when I use f.input. The second menu does not populate with any options though.

2
i would go about using ajax for this case. Rails Ajax, still use your jquery to detect a change in the first select list, then trigger a controller action via ajax to reload the second listlusketeer
If the second field's data is not being changed by some form of admin panel, then you can easily implement this in your JS. Otherwise you need to use AJAX to fetch the options for second field once the user has selected the first field.Jagjot
@JagjotSingh - the subcategories for each category are defined in my helper.Mel
@Mel that means they will be pretty static and not changing much right?Jagjot
That's right - they are unlikely to changeMel

2 Answers

1
votes

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>
1
votes

Use this code, edit it for your #id's and urls

// Dynamic Search Model Loader
$(function(){
    var saved = {};
    var options, value;
    $("#selectOne").change(function(){
        options = '<option value="" selected="selected">Loading...</option>';
        $("#selectTwo").html(options);
        value = $(this).val();

        if (saved[value].length > 0) {
           $("#selectTwo").html(saved[value]);
        } else {
          $.ajax({
            url: '/api/get-json-values/?category=' + value, // Set the category as the value or whatever you want it to be
            type: 'GET',
            dataType: 'json',
            async: true
        }).done(function(data){
            options = '<option value="" selected="selected">Please Pick...</option>';
            // Parse through the values
            $.each(data, function(text){
                options += '<option value="' + text + '">' + text + '</option>';
            });
            // Populate the second select menu
            $("#selectTwo").html(options);
            saved[value] = options;
          }).fail(function(data){
            options = '<option value="" selected="selected">Empty...</option>';
            $("#selectTwo").html(options);
          }); // End Ajax
         };
    }); // End Change
});

Repeat it as needed for the other select menus


Requires you to create an action in your controller that returns a JSON with the values you want.

** Routes.rb **

namespace :api do
    get 'get-json-values', to: 'queries#category_search'
end

Controller Code:

class API::Queries < ApplicationController

  def category_search
    category = params[:category]

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

    if @json
      render json: @json.to_json, status: 200
    else
      render json: {message: 'Not Found'}, status: 404
    end
  end
end

It will then go though them and add them as options to the slave select menu