Display just the subcategories of the chosen categ

2019-06-27 11:21发布

问题:

This is the code I currently have:

<%= f.collection_select :category_id, Category.all, :id, :name, {prompt: "Choose a category"} %>
<%= f.collection_select :subcategory_id, Subcategory.all, :id, :name, {prompt: "Choose a subcategory"} %>

All it does is to display in one dropdown all the categories and in another all the subcategories. like this

Question: How to make just the specific subcategories appear depending on the chosen main category. With the code above, it shows all the categories and all subcategories.

Everything is linked in the model, the has_many, and belongs_to...and category_id and subcategory_id..everything is working properly, I just don't know how to show the specific subcategories within chosen category.

My attempt:

  <% if (Category.where(:name=>"Intro")) do |d| %>
    <% d.subcategories.each do |subcategory| %>
      <%= link_to subcategory.name, gigs_path(subcategory: subcategory.name) %>
    <% end %>
  <% end %>

This code gives an error. I though to say if, for example, the user picks the category called "Intro" than list all the "Intro subcategories". But it didn't work out - my code is obviously wrong.

Thank you.

回答1:

I assume you want this to happen without reloading the page? You won't get around a bit of JavaScript then. Here's the general workflow:

  • when the category dropdown is changed, send an Ajax request which will respond with JavaScript and insert the correct values into the Subcategory dropdown.

For the sake of this example I will just assume that your form (the model that belongs to a category) is a post. You didn't seem to mention the model.

Let's try this:

views/posts/_form:
I will wrap the subcategories selct box in a subcategories_select div, which enables us to replace the whole content later.

<%= f.collection_select :category_id, Category.all, :id, :name, { prompt: "Choose a category" }, id: "category_id" %>

<div id="subcategories_select">
 <%= f.collection_select :subcategory_id, Subcategory.all, :id, :name, { prompt: "Choose a subcategory" }, { disabled: true } %>
</div>

assets/javascripts/posts.js.erb

# select the element to watch for changes
$('form').on('change', '#category_id'), function() {
  var category_id = $(this).val(); # save the category_id set in the first dropdown

  $.ajax({
    url: "/categories/" + category_id + "/get_subcategories", # a custom route, see routes.rb further down
    type: "GET",
    dataType: "script", # we expect a response in js format
    data: { "category_id": category_id } # the only value we will need to get the subcategories
  });
});

config/routes.rb

# we need a custom route for our get_subcategories action
resources :categories do
  member do
    get :get_subcategories, defaults: { format: "js" }
  end
end

controllers/posts_controller.rb
Important: I assume that a category has_many subcategories and subcategories have a category_id, which meansbelong_to a category. If that's not the following won't make sense.

# our custom controller action
def get_subcategories
  @subcategories = Subcategory.where(category_id: params[:category_id])
end

app/views/posts/get_subcategories.js.erb
Here we will replace the content of our subcategories_select div and insert a collection_select with the appropriate options.

$('#subcategories_select').html("<%= j collection_select(:post, :category_id, @subcategories, :id, :title), { prompt: 'Select subcategory...' }, { disabled: false } %>");

Please note that I did this from the top of my head, so there might be errors, but this is one approach to dynamically populate select boxes, or in general change anything on a page without reloading.



回答2:

This is what solved my question

in javascript folder (masonry gem must be installed)

$ ->
  $('#gigs').imagesLoaded ->
    $('#gigs').masonry
      itemSelector: '.box'
      isFitWidth: true

  $(document).on 'change', '#gig_category_id', (evt) ->
    $.ajax 'update_sub_categories',
      type: 'GET'
      dataType: 'script'
      data: {
        category_id: $("#gig_category_id option:selected").val()
      }
      error: (jqXHR, textStatus, errorThrown) ->
        console.log("AJAX Error: #{textStatus}")
      success: (data, textStatus, jqXHR) ->
        console.log("Dynamic country select OK!")

in Gig controller

respond_to :html, :js, :json
  def update_sub_categories
    @cats = Subcategory.where(category_id: params[:category_id]).all
    respond_with(@cats)
  end

Than i created a partial in gig view _subcategory.html.erb and put this code

<option value="<%= cat.id %>"><%= cat.name %></option>

than another partial in gig view called _update.html.erb put this code

$("#gig_subcategory_id").empty().append("<%= escape_javascript(render(:partial => "subcategory", :collection => @cats, :as => :cat)) %>")

Finally in the view to display the category and subcategory,i used

  <%= f.collection_select :category_id, Category.all, :id, :name, {prompt: "Choose a category"} %>
  <%= f.collection_select :subcategory_id, Subcategory.all, :id, :name, {prompt: "Choose a subcategory"} %>


回答3:

Filtering a collection_select with js may be very difficult. I recommend using group_collection_select which filters the data if the relations are correct. Asumming you have a data model that looks something like the one mentioned in the above answer, the view should have something like this:

views/posts/_form:

<%= f.collection_select(:category_id, Category.all, :id, :name,    
               { prompt: 'Select a category' }, { id: 'category-select' }) %>

<%= f.grouped_collection_select :subcategory_id, Category.all, :subcategories, 
          :name, :id, :name, { include_blank: 'Select a sub category' },
                                               { id: 'subcategory-select' } %>

Right now you will be able to see both select forms but the grouped_collection_select box shows kind of nested options. in order to show only the necesary subcategories we will need to do some changes in the javascript, in my case y use coffee and my files will be named .coffee instead of .js.erb

app/assets/javascripts/posts.coffee:

jQuery ->
  subcat = $('#subcategory-select').html()
  $('#category-select').change ->
    cat = jQuery('#category-select').children('option').filter(':selected').text()
    options = $(subcat).filter("optgroup[label='#{cat}']").html()
    if options
      $('#subcategory-select').html(options)
    else
      $('#subcategory-select').empty()

I owe my answer to this source where you can find more details about using group_collection_select



回答4:

So the embedded ruby that you wrote gets evaluated before being sent to the 'client side'. What that means is that the code you wrote gets turned into static HTML and sent to the user.

What you want is to use something that can provide logical operations on the user's browser. That means javascript.

Checkout: AngularJS: https://angularjs.org/