Dynamic and AJAX-driven Select Lists in Webform
This example is a two-for-one deal! I’m going to demonstrate how to
- dynamically populate a webform select list on page load
- dynamically populate a webform select list based upon the value chosen in another webform select list
For both parts of this example, the demo form I’ll be using is a “Get Started” form.
Part 1
This is actually quite simple given that Webform has the appropriate hooks to do this. We’re going to be using the hook_webform_select_options_info() hook. You can read about this at http://drupal.org/node/406486. In this example, to populate the “Select a Campus Location” select list we want to query the database for all published location nodes (location is the name of the Campus Locations content type). To populate the “Select a Program” select list, we’ll be doing a similar thing for the initial page load, but we’ll also be re-populating this list after the user chooses a campus location. The specification for the form called for the two select lists to be optional. So, initially, we want the user to be able to pick any program or campus location, but if the user chooses a campus location, we need to then limit the programs list to programs related to this location. The relationship is achieved through a multi-select node reference field within program that points to one or more locations.
Here’s how we use the aforementioned hook_webform_select_options_info() for each of these fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/** * Provide webform list options * See <a href="http://drupal.org/node/406486">http://drupal.org/node/406486</a> and <a href="http://groups.drupal.org/node/71588">http://groups.drupal.org/node/71588</a> * and <a href="http://bit.ly/sAVUzg">http://bit.ly/sAVUzg</a> */ function mymodule_webform_select_options_info() { $items['programs'] = array( 'title' => 'Programs', 'options callback' => 'mymodule_get_programs_for_select_list', ); $items['locations'] = array( 'title' => 'Locations', 'options callback' => 'mymodule_get_locations_for_select_list', ); return $items; } /** * Get an array of program nodes (nid => program title) * * @return * Array of programs: nid => program title */ function mymodule_get_programs_for_select_list() { $nodes = array(); $results = db_query('SELECT nid, title FROM {node} WHERE type = "program" ORDER BY title'); while ($node = db_fetch_object($results)) { $nodes[$node->nid] = $node->title; } return $nodes; } /** * Get an array of location nodes (nid => location title) * * @return * Array of locations: nid => location title */ function mymodule_get_locations_for_select_list() { $nodes = array(); $results = db_query('SELECT nid, title FROM {node} WHERE type = "location" ORDER BY title'); while ($node = db_fetch_object($results)) { $nodes[$node->nid] = $node->title; } return $nodes; } |
After saving this code, we can browse to the settings page for each of the fields. The additional options show up under the Load a pre-built option listsetting. We can now set each of them accordingly. Once set, when the form is viewed, the callback functions will be used to populate the two select lists.
The webform now has dynamic “Select a Campus Location” and “Select a Program” dropdowns. I should note that I used jQuery to change the initial values of these fields. By default, they had an initial selected option of “– None –”
1 2 |
$("#webform-client-form-36 #edit-submitted-location option:first").html('Select a Campus Location'); $("#webform-client-form-36 #edit-submitted-program option:first").html('Select a Program'); |
Part 2
Now that we have the options we’d like in each of the two select lists, let’s take care of the AJAX piece. Admittedly there are probably cleaner, more efficient ways of adding this functionality, but I’ll let you choose if this method is for you or not.
In a nutshell what we’re going to do is look for changes to the “Select a Campus Location” select list. Once the user changes the value, we need to use AJAX to ask Drupal for the available programs at the chosen location. The callback function will pass back to jQuery the new select list options as a single string of HTML. This is the part where there may be a better way to do this. Once jQuery gets the results back from Drupal, we’ll just replace all of the select options with the new ones.
The first step is to create a menu item that will handle our AJAX request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Implementation of hook_menu(). */ function mymodule_menu() { $items = array(); $items['location-programs/js'] = array( 'page callback' => 'mymodule_get_programs_select_options', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } |
Next, we need to create the callback function itself. Note that we’ll be passing the node ID of the chosen location as an argument/parameter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * AJAX callback that returns HTML code for all of the available programs * as <option> elements (see script.js) * * @param $nid * The location node id * @return * HTML <options> for all available programs at this location */ function mymodule_get_programs_select_options($nid) { $nodes = array(); $results = db_query('SELECT DISTINCT(node.nid) AS nid, node.title AS node_title FROM node node LEFT JOIN content_field_program_locations field_program_locations ON node.vid = field_program_locations.vid INNER JOIN node node_locations ON field_program_locations.field_program_locations_nid = node_locations.nid WHERE node.status 0 AND node.type = "program" AND node_locations.nid = %d GROUP BY nid ORDER BY node_title ASC', $nid); $output = '<option value selected="selected">Select a Program</option>'; while ($node = db_fetch_object($results)) { $output .= "<option value='{$node->nid}'>{$node->node_title}</option>"; } return drupal_json(array('html_output' => $output)); } |
Lastly, we need to write some jQuery code to look for the select list change, grab the associated programs, and modify the program select list. I put this into my script.js file, but you may choose to make a separate javascript file for this. If so, this is demonstrated at the end of the tutorial athttp://viziontech.co.il/tutorial1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Drupal.behaviors.mytheme = function(context) { // AJAX GET STARTED FORM $("#webform-client-form-36 #edit-submitted-location").change(function(){ var nid = $('#webform-client-form-36 #edit-submitted-location option:selected').attr('value'); // Provide temporary message until results received $("#webform-client-form-36 #edit-submitted-program").html('<option value selected="selected">Retrieving programs, please wait...</option>'); // Get the programs for this location $.get('/location-programs/js/' + nid, null, updatePrograms); return false; }); var updatePrograms = function(response) { var result = Drupal.parseJson(response); $("#webform-client-form-36 #edit-submitted-program").html(result.html_output); } // END AJAX GET STARTED FORM }; |
Note that we’re using a GET request here. There are some good tutorials on using POST instead. Here is one of my favorites: http://viziontech.co.il/tutorial1
Conclusion
There are undoubtedly many different ways to achieve the same behavior, so please explore the alternatives. You may also want to implement some sort of error handling on the AJAX callback to make sure something useful happens if there is a problem. It’s been a long day, so I’m going to wrap this up. Thanks for reading!
3 Comments
nicholas
What about getting a user instead of node?
adam
Nicholas, there’s no reason you couldn’t do that. You’d just have to change the queries a bit. Without knowing your specific needs, that’s all I can really say about it. Cheers!
Paul
Great write up!
I know this is an old post but hope someone can help out. I have the same exact needs as you had (dynamically populate a webform select list based upon the value chosen in another webform select list) except my data is stored in a mysql dbase table – and not as nodes. For Part 1 I have the following code:
function exhibitors_webform_select_options_info() {
$items[‘company’] = array(
‘title’ => ‘Company’,
‘options callback’ => ‘exhibitors_get_company_for_select_list’,
);
$items[‘booth’] = array(
‘title’ => ‘Booth’,
‘options callback’ => ‘exhibitors_get_booth_for_select_list’,
);
return $items;
}
function exhibitors_get_company_for_select_list() {
$options = array();
$sql = “SELECT company FROM {exhibitors} ORDER BY company”;
$result = db_query($sql);
foreach ($result as $row) {
$options[$row->company] = $row->company;
}
return $options;
}
function exhibitors_get_booth_for_select_list() {
$options = array();
$sql = “SELECT booth FROM {exhibitors} ORDER BY company”;
$result = db_query($sql);
foreach ($result as $row) {
$options[$row->booth] = $row->booth;
}
return $options;
}
Above works perfectly. I have my 2 Select Lists available in Webform.
What I would like to do is when a user chooses a Company on the drop down the Booth field populates with the corresponding number that is on the same row as the company under the booth column.
Where I am stuck is the call back function. Your example is using the node id as the paramater/argument but since I am not using nodes am not sure how to go about doing this.
Any help is appreciated.
Thanks!