How to create dynamically populated cascading dropdown-lists for Contact Form 7

Something I had to deal with quite often lately, is creating dynamic drop-down lists in WordPress, like this:

    As always, I like to use the WordPress plugin Contact Form 7 as a basis for all my form-driven projects. The reason is that Contact Form 7 has a robust validation mechanism. It is very lightweight, yet powerful and easy to use for both intermediate and advanced users. And lastly, because there is a great many plugins that build on top of CF7. These plugins allow you to turn CF7 in exactly the solution you need for your project. Nothing more, nothing less. But enough praising CF7, let’s build a form already!

    The first thing you will need to do is get your data-set. For this example I will use a CSV file that is structured like this:

    Let’s save this file as make-model-year.csv to our /wp-content/uploads/ folder.

    Server side script

    Now, let’s write our server side function, that will be called trough AJAX. You can add this function at the bottom of your theme’s functions.php file:

    Server side script (PHP). Add this to functions.php

    <?php
    function ajax_cf7_populate_values() {
    
            // read the CSV file in the $makes_models_years array
    
    	$makes_models_years = array();
    	$uploads_folder = wp_upload_dir()['basedir'];
    	$file = fopen($uploads_folder.'\make_model_year.csv', 'r');
    
    	$firstline = true;
    	while (($line = fgetcsv($file)) !== FALSE) {
    		if ($firstline) {
    			$firstline = false;
    			continue;
    		}
    		$makes_models_years[$line[0]][$line[1]][] = $line[2];
    
    	}
    	fclose($file);
    
            // setup the initial array that will be returned to the the client side script as a JSON object.
    
    	$return_array = array(
                'makes' => array_keys($makes_models_years),
                'models' => array(),
                'years' => array(),
                'current_make' => false,
                'current_model' => false
            );
    
            // collect the posted values from the submitted form
    
    	$make = key_exists('make', $_POST) ? $_POST['make'] : false;
    	$model = key_exists('model', $_POST) ? $_POST['model'] : false;
    	$year = key_exists('year', $_POST) ? $_POST['year'] : false;
    
            // populate the $return_array with the necessary values
    
    	if ($make) {
    		$return_array['current_make'] = $make;
    		$return_array['models'] = array_keys($makes_models_years[$make]);
    	    if ($model) {
    		$return_array['current_model'] = $model;
    		$return_array['years'] = $makes_models_years[$make][$model];
    		if ($year) {
    	            $return_array['current_year'] = $year;
    	        }
                }
            }
    
            // encode the $return_array as a JSON object and echo it
            
            echo json_encode($return_array);
            wp_die();
    
    }
    
    // These action hooks are needed to tell WordPress that the cf7_populate_values() function needs to be called
    // if a script is POSTing the action : 'cf7_populate_values'
    
    add_action( 'wp_ajax_cf7_populate_values', 'ajax_cf7_populate_values' );
    add_action( 'wp_ajax_nopriv_cf7_populate_values', 'ajax_cf7_populate_values' );

    Don’t worry, if this does not make much sense to you yet. The function by itself will not do much, except read the CVS data, and returning it to the client side script (JavaScript).

    The client side script

    You should add this after the form is loaded. Typically you would create a custom page-template where you would include the form, or you could use an action hook to output the script, or add it to a seperate .js file. But for simplicity’s sake, we’ll just add it to footer.php, right before the </body> tag. So without further ado, here’s the script.

    Client side script (JavaScript). Add this to footer.php

    <script>
        (function($) {
    
            // create references to the 3 dropdown fields for later use.
    
            var $makes_dd = $('[name="makes"]');
            var $models_dd = $('[name="models"]');
            var $years_dd = $('[name="years"]');
    
    
            // run the populate_fields function, and additionally run it every time a value changes
    
            populate_fields();
            $('select').change(function() {
                populate_fields();
            });
    
            function populate_fields() {
    
                var data = {
    
                    // action needs to match the action hook part after wp_ajax_nopriv_ and wp_ajax_ in the server side script.
    
                    'action' : 'cf7_populate_values', 
    
                    // pass all the currently selected values to the server side script.
    
                    'make' : $makes_dd.val(),
                    'model' : $models_dd.val(),
                    'year' : $years_dd.val()
                };
    
                // call the server side script, and on completion, update all dropdown lists with the received values.
    
                $.post('/wp-admin/admin-ajax.php', data, function(response) {
                    all_values = response;
    
                    $makes_dd.html('').append($('<option>').text(' -- choose make -- '));
                    $models_dd.html('').append($('<option>').text(' -- choose model  -- '));
                    $years_dd.html('').append($('<option>').text(' -- choose year -- '));
    
                    $.each(all_values.makes, function() {
                        $option = $("<option>").text(this).val(this);
                        if (all_values.current_make == this) {
                            $option.attr('selected','selected');
                        }
                        $makes_dd.append($option);
                    });
                    $.each(all_values.models, function() {
                        $option = $("<option>").text(this).val(this);
                        if (all_values.current_model == this) {
                            $option.attr('selected','selected');
                        }
                        $models_dd.append($option);
                    });
                    $.each(all_values.years, function() {
                        $option = $("<option>").text(this).val(this);
                        if (all_values.current_year == this) {
                            $option.attr('selected','selected');
                        }
                        $years_dd.append($option);
                    });
                },'json');
            }
    
        })( jQuery );
    </script>

    The Form

    And the contact form 7 form itself? Simple! just add 3 select boxes with the names “makes”, “models” and “years”.

    [select makes]
    [select models]
    [select years]

    Done!