James Lowrey James Lowrey - 2 months ago 6
Javascript Question

Populate a drop down using result from a previous drop down

I have a Rails database with Schools and Majors in it. Once the user selects his school, I want to fill up the major's drop down with only the majors from the corresponding school. Currently I have two working drop downs, but as you can see they just select all values. Will I need Json/Ajax/JavaScript for this, and if so how can I go about implementing it?

<div class="field">
<%= f.label :school_id %><br>
<%= f.collection_select :school_id, School.order(:name),:id,:name, include_blank: false %>
</div>
<div class="field">
<%= f.label :major_id %><br>
<%= f.collection_select :major_id, Major.order(:name),:id,:name, include_blank: false %>
</div>

Answer

Alright, so after much searching I've arrived at a working AJAX request (which I will post here for posterity).

When creating a new course in my database there is a Schools and a Major foreign key that must be non-null. Here are the dropdowns

<!-- app/views/courses/_form.html.erb -->
<div class="field">
  <%= f.label :school_id %><br>
  <%= f.collection_select :school_id, School.order(:name),:id,:name, {include_blank: false},{:id=>'school_id_select'} %>
</div>
<div class="field">
  <%= f.label :major_id %><br>
  <%= f.collection_select :major_id, [],:id,:name, {include_blank: false},{:id=>'major_id_select'} %>
</div>

Now that the HTML is up, we need some JavaScript (and jQuery) to process the AJAX request

//app/assests/javascripts/courses.js (note this was generated as a .coffee file, which I changed to .js as I don't know CoffeeScript
console.log('loaded courses.js')//check that the file is loaded

$(document).on('change','#school_id_select', function () {//change majors when user changes school
   load_majors_from_school_dropdown();
});

$(document).ready(load_majors_from_school_dropdown);//populate majors when page loads with first school

function load_majors_from_school_dropdown(){
    var request = "/majors/find_majors_given_school_id?school_id=" //access controller of interest
        + $('#school_id_select').val();

    var aj = $.ajax({
        url: request,
        type: 'get',
        data: $(this).serialize()
    }).done(function (data) {
         change_majors(data);//modify the majors' dropdown
    }).fail(function (data) {
         console.log('AJAX request has FAILED');
    });
};

//modify the majors' dropdown
function change_majors(data) { 
    $("#major_id_select").empty();//remove all previous majors
    for(i = 0;i<data.length;i++){ 
        $("#major_id_select").append(//add in an option for each major
            $("<option></option>").attr("value", data[i].id).text(data[i].name)
        );
    }
};

Take note of the 'request' variable. This indicates the controller & action that will process the request. Now we must make it. My Major controller already existed, so we only have to add the action. First add the action's route to make the Rails server aware of the action.

#config/routes.rb
Rails.application.routes.draw do
  #...
  get 'majors/find_majors_given_school_id'
  #...
end

Now create the action in the Major controller

#app/controllers/majors_controller.rb
#custom action for AJAX/JSON calls. Note this can be done multiple ways
def find_majors_given_school_id
   school_id = params[:school_id]#utilizes the url to extract school_id ".../find_majors_given_school_id?school_id=123123"
   puts "THIS IS MY SCHOOL ID :: #{school_id}"#view this in teminal

   majors = Major.search_for_school_id(school_id).as_json#query the model for the data and convert it to a hash using as_json
   puts "THESE ARE MY MAJORS IN A HASH :: #{majors}"
   respond_to do |format|
        format.json { 
            render json: majors
        } 
   end
end

Finally, we have to create the query to extract this information in our model.

#app/models/major.rb
class Major < ActiveRecord::Base
    has_many :courses, dependent: :destroy
    belongs_to :school, :foreign_key => "school_id"

    def self.search_for_school_id(id)
        where("school_id = ?",id)        
    end
    #...
end

I think that's everything. There are certainly other ways of implementing this, but this one worked for me. Good luck!