How to change the content of one html dropdown based on the selection made in another

Dynamic Dependent Dropdown filtering with Codeigniter and jQuery

JQuery, Javascript, CodeIgniter, PHP

Say you're building a form in which users can create a person, and assign that person a city to live in. But that city field needs to be populated depending on the state the person lives in, otherwise the city field will be enormously long (and there is more of a chance for user error and confusion). What you need is dynamic dependent dropdowns. Triple Ds! In order to accomplish this using Code Igniter, this is what I did:

HTML (view_form_all):


<?php echo form_open('control_form/add_all'); ?>
        <label for="f_state">State<span class="red">*</span></label>
        <select id="f_state" name="f_state">
            <option value=""></option>
            <?php
            foreach($states as $state){
                echo '<option value="' . $state->id . '">' . $state->state_name . '</option>';
            }
            ?>
        </select>
        <label for="f_city">City<span class="red">*</span></label>
        <!--this will be filled based on the tree selection above-->
        <select id="f_city" name="f_city" id="f_city_label">
            <option value=""></option>
        </select>
        <label for="f_membername">Member Name<span class="red">*</span></label>
        <!--<input type="text" name="f_membername"/>-->
<?php echo form_close(); ?>

This code was put into a code igniter view file called view_form_all. Here, code igniter sets open and closing form tags, and looks to a controller called control_form to run a function called add_all. add_all will make available to us a list of all the states in our database.

CONTROLLER (control_form/add_all) :


function add_all(){

        #Validate entry form information
        $this->load->model('Model_form','', TRUE);
        $this->form_validation->set_rules('f_state', 'State', 'required');
        $this->form_validation->set_rules('f_city', 'City', 'required');
        $this->form_validation->set_rules('f_membername', 'Member Name', 'required');

        $data['states'] = $this->Model_form->get_state(); //gets the available groups for the dropdown

        if ($this->form_validation->run() == FALSE)
        {
            $this->load->view('view_form_all', $data);
        }
        else
        {
            #Add Member to Database
            $this->Model_form->add_all();
            $this->load->view('view_form_success');
        }
    }

Here I load the model to retrieve and send data to the database, and set up some basic validation for the required fields. Then I query the controller for a couple of functions ( get_state() and get_city() ) in order to populate the dropdowns. If validation has not yet been run, I load the view so that the user can fill out the form. If it has been run, and passed, I run the "add_all" function to add the member to the database, and display a success page. Now lets look at the model:

MODEL (Model_form/get_state) :


<?php
class Model_form extends CI_Model
{
    function __construct()
    {
            // Call the Model constructor
            parent::__construct();
    }

    function get_state(){
        $query = $this->db->query('SELECT id, state_name FROM state');
        return $query->result();
    }

}

The first part of the code is the standard way of creating a model with the appropriate name. I included this for reference. the function, get_state is what we're really concerned with here. In this function, I simply query the database to select all states and their relevant IDs, and return them back to the controller.

Now let's look at the add_all function of the model:


function add_all()
    {
                $v_state = $this->input->post('f_state');
        $v_membername = $this->input->post('f_membername');

        $data = array(
                    'id' => NULL,
                    'state' => $v_state,
                'member_name' => $v_membername,
        );

        $this->db->insert('members', $data);
    }

This form simply stores the data from the form in variables, and submits it to the database. So how does that state field get populated? We'll do this with javascript. Back in the view_form_all view, below all the html, let's set up our script:

JAVASCRIPT (view_form_all)


$('#f_city, #f_city_label').hide();
$('#f_state').change(function(){
    var state_id = $('#f_state').val();
    if (state_id != ""){
        var post_url = "/index.php/control_form/get_cities/" + state_id;
        $.ajax({
            type: "POST",
            url: post_url,
            success: function(cities) //we're calling the response json array 'cities'
            {
                $('#f_city').empty();
                $('#f_city, #f_city_label').show();
                $.each(cities,function(id,city)
                {
                    var opt = $('<option />'); // here we're creating a new select option for each group
                    opt.val(id);
                    opt.text(city);
                    $('#f_city').append(opt);
                });
            } //end success
         }); //end AJAX
    } else {
        $('#f_city').empty();
        $('#f_city, #f_city_label').hide();
    }//end if
}); //end change

Now I'm assuming you've already included JQuery in the header of your page. If you don't know how to do that, check it out at jquery.com. The first thing we do is hide our city form fields, since they can't be populated until the state is chosen. The next thing we do is tell JQuery to listen for when the state field is changed. As long as it's not blank, we have it post whatever the selected option was to our controller, into a function called get_cities(), with the third parameter being the ID of the state chosen.

If we find a match for that state ID, we're going to populate a dropdown field with all the cities that match that state ID. Regardless of whether we find a match or not, we want to make sure to clear out the cities dropdown, in case it has been run previously. If there is no match, we want to re-hide the fields.

So, the only thing left to do is take a look at that get_cities() function in our controller, and see what it's doing:

CONTROLLER (get_cities)


function get_cities($state){
        $this->load->model('Model_form','', TRUE);
        header('Content-Type: application/x-json; charset=utf-8');
                echo(json_encode($this->Model_form->get_cities_by_state($state)));
    }

Here we tell the controller to query the model for all cities matching the state ID, and return that data as JSON so it can be read and understood by JQuery. So in the model:

MODEL (get_cities_by_state):


function get_cities_by_state ($state){
        $this->db->select('id, city_name');


        $query = $this->db->get('cities');
        $cities = array();

        if($query->result()){
            foreach ($query->result() as $city) {
                $cities[$city->id] = $city->city_name;
            }
            return $cities;
        } else {
            return FALSE;
        }
    }

Here, we're sealing the deal by telling code igniter to grab all the cities from the database with a state ID that matches the one selected in the dropdown by the user. We return it as an array back to the controller, and the controller interprets it and sends it back to our view. Classic MVC pattern, and voila, dynamic dropdowns.

Thanks to Stephan Hoppe for pointing out a couple code errors that have been sitting in here for years...