Creating linked dropdowns using jQuery and PHP

Julian Hansen
CERTIFIED EXPERT
Published:
This article discusses how to create an extensible mechanism for linked drop downs.

Overview

A link drop down is one whose data is dependent on the state of another drop down. For instance Category defines subcategory, make defines model etc. Changing a value in one drop down changes the available items in a linked drop down.

The solution should be extensible, i.e. we should be able to use it on any number of drop downs.
We will need a mechanism to identify the drop downs that will be the primary source of data and we need a mechanism to identify which drop down must be refreshed when the primary changes.

To do this we will make use of a class and a custom-attribute.

The class will be used to identify those controls that need to trigger an update when their state changes. Lets call this class linked-dropdown.

We use a custom data attribute to determine which dependent drop down to target when an onchange fires on the linked-dropdown. The reason behind using a custom data attribute is that it is easy to retrieve and it does not interfere with classes which should be used for styling and selecting. The data attribute we will use for this example is data-linked.
 

Markup

For this article I have chosen to use a Product / Make / Model hierarchy. Based on this let's put together some markup to show the 3 <select> boxes we will need to implement the requirement.
 
  <form class="form form-horizontal">
                          Get from server <input type="checkbox" name="server" id="server" /><br/>
                          <div class="form-group">
                            <label for="company" class="col-sm-2 control-label">Company</label>
                            <div class="col-sm-10">
                              <select id="company" name="company" class="form-control linked-dropdown" data-linked="make">
                                <option value="">-- Select Product --</option>
                                <option value="1">Product1</option>
                                <option value="2">Product2</option>
                                <option value="3">Product3</option>
                              </select>
                            </div>
                          </div>
                          <div class="form-group">
                            <label for="make" class="col-sm-2 control-label">Make</label>
                            <div class="col-sm-10">
                              <select id="make" name="make" class="form-control linked-dropdown" data-linked="model"></select>
                            </div>
                          </div>
                          <div class="form-group">
                            <label for="model" class="col-sm-2 control-label">Model</label>
                            <div class="col-sm-10">
                              <select id="model" name="model" class="form-control"></select>
                            </div>
                          </div>
                        </form>

Open in new window




image.gifimage.gifimage.gifimage.gif In the above code I have created 3 form-groups (I am using Bootstrap for the layout but it is not necessary for this solution to work). Each group contains one of the <select>'s that will make up our Product / Make / Model working set. As you can see the first (Product) <select> is already populated. As it is at the root of the hierarchy it is not dependent on any other control and can therefore be populated.

Take a look at how each of the <select>'s is defined.

Product

<select id="company" name="company" class="form-control linked-dropdown" data-linked="make">

Open in new window


Make

<select id="make" name="make" class="form-control linked-dropdown" data-linked="model"></select>

Open in new window


Model

<select id="model" name="model" class="form-control"></select>

Open in new window


You will notice that Product and Make are mostly the same. They both contain the .linked-dropdown class and they both have a data-linked attribute.

The Product <select>'s data-linked attribute is set to make which happens to be the id of the Make <select>.
The data-liked attribute for the Make <select> is set to model which happens to be the id of the model <select>.
You will notice that the Model <select> does not have the class or the data attribute. This is because there is nothing that is dependent on its value.
 

The Data

For this to work we need data. Specifically we need data for the second (Make) and third (Model) <select>'s. We have two options we can use to source this data

Option 1
We embed the data in the JavaScript file as a data structure and do everything locally.
Option 2
We make an AJAX call back to the server to retrieve the options for the targeted <select>

Option 1 may work well with relatively small data sets, however if the data set is large it might be better to make an AJAX call back to the server.We have two options for how we source the data for the drop downs
 

Local Data (option 1)

Let's consider the local data version first. We need to define Make data for each of the Product options and Model data for each of the Product / Make combinations. The logical means to store this is in an array. Here is a sample data set that we will use in this article.

<script>
                      var options = {
                        make: {
                          1:{
                            1: 'Prod1Make1',
                            2: 'Prod1Make2',
                            3: 'Prod1Make3'
                          },
                          2:{
                            4: 'Prod2Make1',
                            5: 'Prod2Make2',
                            6: 'Prod2Make3'
                          },
                          3 :{
                            7: 'Prod3Make1',
                            8: 'Prod3Make2',
                            9: 'Prod3Make3'
                          }
                        },
                        model: {
                          1: {
                            1: 'Prod1Make1Model1',
                            2: 'Prod1Make1Model2',
                            3: 'Prod1Make1Model3'
                          },
                          2: {
                            4: 'Prod1Make2Model1',
                            5: 'Prod1Make2Model2',
                            6: 'Prod1Make2Model3'
                          },
                          3: {
                            7: 'Prod1Make3Model1',
                            8: 'Prod1Make3Model2',
                            9: 'Prod1Make3Model3'
                          },
                          4: {
                            10: 'Prod2Make1Model1',
                            11: 'Prod2Make1Model2',
                            12: 'Prod2Make1Model3'
                          },
                          5: {
                            13: 'Prod2Make2Model1',
                            14: 'Prod2Make2Model2',
                            15: 'Prod2Make2Model3'
                          },
                          6: {
                            16: 'Prod2Make3Model1',
                            17: 'Prod2Make3Model2',
                            18: 'Prod2Make3Model3'
                          },
                          7: {
                            19: 'Prod3Make1Model1',
                            20: 'Prod3Make1Model2',
                            21: 'Prod3Make1Model3'
                          },
                          8: {
                            22: 'Prod3Make2Model1',
                            23: 'Prod3Make2Model2',
                            24: 'Prod3Make2Model3'
                          },
                          9: {
                            25: 'Prod3Make3Model1',
                            26: 'Prod3Make3Model2',
                            27: 'Prod3Make3Model3'
                          }
                        }
                      };
                      </script>

Open in new window


As you can see this is a standard JavaScript object with two properties: one for Make and one for Model.
Within each of these properties we define additional object properties that are made up of a key and an object with a list of values.

The key (in the case of make) refers to the Product ID  that is linked to that set of options. So options.make[1] is the list of Makes linked to product 1.

Why are we using Objects instead of arrays? This is because we cannot guarantee that Product ID's will be consecutive and start from 1. If products are a dynamically added and removed from the database it is quite feasible that the product ID's will become scattered (non-contiguous) which will not work with arrays which work of 0-based consecutive indices.

From the data we begin to get a picture of how this is going to work. We select a product, we use the selected Product ID to retrieve an array of Makes linked to that product and the same from Make to model.
 

JavaScript / jQuery


To make this work we need some JavaScript to link the change events on the <select> boxes to our code that will manage the options in those <select>'s. What we need to do is bind to the onchange event of any select that has the .linked-dropdown class. We then use the context information in the event handler to work out what <select> to update.

Before jumping in it is important to consider how this is going to function. When a <select> higher up the hierarchy changes we need to set the options in the linked <select>. But there is more to it than that. In our example we have a hierarchy 3 levels deep. When we change Product not only does Make need to be updated but all its dependent children need to be updated or cleared as well. If we don't do this and we change Product which results in the options in Make changing but we leave Model as is - it could end up in a data inconsistency.

With that in mind lets look at the code we need to make this work
<script>
                      $(function() {
                        $('.linked-dropdown').change(function() {
                          // GET THE id OF THE NEXT DROPDOWN
                          // IN THE CHAIN
                          var linked = $(this).data('linked');
                          
                          // CREATE THE SELECTOR
                          var target = '#' + linked;
                        
                          // EMPTY THE TARGET
                          $(target).empty();
                          
                          // EMPTY ALL THE CHILDREN
                          var child = $(target).data('linked');
                          while (child) {
                            $('#' + child).empty();
                            child = $('#' + child).data('linked');
                          }
                      
                          // GET THE SELECTED VALUE
                          var val = $(this).val();
                          // AND USE THIS TO GET THE CORRESPONDING
                          // ARRAY OF OPTIONS
                          opts = options[linked][val];
                          
                          // INSERT THE INSTRUCTION OPTION
                          $(target).append($('<option/>').val('').html('-- Select --'));
                            
                          // LOOP THROUGH THE TARGET OPTIONS AND ADD THEM
                          $.each(opts, function(v,o) {
                            $(target).append($('<option/>').html(o).val(v))
                          });
                        });
                      });
                      </script>

Open in new window



There is quite a bit of code but the comments should give an idea of what we are doing.
  •   First we get the id of the linked <select>
  •  We empty it
  •  We check to see if it has a dependent and empty that and so on in the loop.
  •  We then get the array of options from our static object that match the selected criteria of the parent <select> and use those to add <option>'s  to the target.
 

Server Data (option2)

What if we want to get the data from the server? Two things need to change.
1. We need to change the script above to use an AJAX request to send the selected value to the server and receive back a list of options to add to the child.
2. We need server side code to interpret the request and build a list of items to send back.

Let's look at the client side (AJAX) code first. The first part is the same as the script above - it is just in the populating section that things change.
$(function() {
                        $('.linked-dropdown').change(function() {
                          // GET THE id OF THE NEXT DROP DOWN IN THE CHAIN
                          var linked = $(this).data('linked');
                          
                          // CREATE THE SELECTOR
                          var target = '#' + linked;
                        
                          // EMPTY THE TARGET
                          $(target).empty();
                          
                          // EMPTY ALL THE CHILDREN
                          var child = $(target).data('linked');
                          while (child) {
                            $('#' + child).empty();
                            child = $('#' + child).data('linked');
                          }
                      
                          // EVERYTHING UP TO HERE IS UNCHANGED
                      
                          // SERVER SEND AJAX CALL AND POPULATE WITH RESPONSE
                          $.ajax({
                            url: 'getoptions.php',
                            data: {
                              linked: linked,
                              value: $(this).val()
                            },
                            type: "POST",
                            dataType: "html"
                          }).then(function(resp) {
                            // SERVER BUILDS HTML RETURN WE JUST INSERT IT
                            $(target).html(resp);
                          });
                        });
                      });

Open in new window

It is important to note that there are many ways to skin a cat. The above code is just one example of how to deal with the return of the data from the server. In this case the server renders the <option> markup and sends it back. The AJAX done() callback can then just add it to the child. Another option here might have been to receive a JSON return and then add the options with JavaScript - which method you prefer is up to you.

All that is missing from the above solution is a server script to return the data. The sample script below also uses static data but the principle remains the same as if the data was retrieved from a database. The script receives an ID (value) and a target (linked) that determines which options must be returned.
The script runs a loop to create the <option> output and sends this back to the browser.
<?php
                       
                      $options = array (
                        'make' => array (
                          1 => array (
                            1 => 'Server-Prod1Make1',
                            2 => 'Server-Prod1Make2',
                            3 => 'Server-Prod1Make3'
                          ),
                          2=> array (
                            4=> 'Server-Prod2Make1',
                            5=> 'Server-Prod2Make2',
                            6=> 'Server-Prod2Make3'
                          ),
                          3 => array (
                            7=> 'Server-Prod3Make1',
                            8=> 'Server-Prod3Make2',
                            9=> 'Server-Prod3Make3'
                          )
                        ),
                        'model' => array (
                          1=> array (
                            1=> 'Server-Prod1Make1Model1',
                            2=> 'Server-Prod1Make1Model2',
                            3=> 'Server-Prod1Make1Model3'
                          ),
                          2=> array (
                            4=> 'Server-Prod1Make2Model1',
                            5=> 'Server-Prod1Make2Model2',
                            6=> 'Server-Prod1Make2Model3'
                          ),
                          3=> array (
                            7=> 'Server-Prod1Make3Model1',
                            8=> 'Server-Prod1Make3Model2',
                            9=> 'Server-Prod1Make3Model3'
                          ),
                          4=> array (
                            10=> 'Server-Prod2Make1Model1',
                            11=> 'Server-Prod2Make1Model2',
                            12=> 'Server-Prod2Make1Model3'
                          ),
                          5=> array (
                            13=> 'Server-Prod2Make2Model1',
                            14=> 'Server-Prod2Make2Model2',
                            15=> 'Server-Prod2Make2Model3'
                          ),
                          6=> array (
                            16=> 'Server-Prod2Make3Model1',
                            17=> 'Server-Prod2Make3Model2',
                            18=> 'Server-Prod2Make3Model3'
                          ),
                          7=> array (
                            19=> 'Server-Prod3Make1Model1',
                            20=> 'Server-Prod3Make1Model2',
                            21=> 'Server-Prod3Make1Model3'
                          ),
                          8=> array (
                            22=> 'Server-Prod3Make2Model1',
                            23=> 'Server-Prod3Make2Model2',
                            24=> 'Server-Prod3Make2Model3'
                          ),
                          9=> array (
                            25=> 'Server-Prod3Make3Model1',
                            26=> 'Server-Prod3Make3Model2',
                            27=> 'Server-Prod3Make3Model3'
                          )
                        )
                      );
                       
                      $val = isset($_POST['value']) ? intval($_POST['value']) : false;
                      $linked = isset($_POST['linked']) ? $_POST['linked'] : false;
                      if ($val && $linked) {
                        $opt = $options[$linked][$val];
                        $return = <<< OPTION
                            <option value="">-- Select --</option>
                       
                      OPTION;
                        foreach($opt as $v => $o) {
                          $return .= <<< OPTION
                            <option value="{$v}">{$o}</option>
                      OPTION;
                        }
                        
                        echo $return;
                      }

Open in new window

That is all there is to it.

You can see a working sample of the above code here
0
3,602 Views
Julian Hansen
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.