Drupal Webforms With Dynamic Select Boxes Tutorial

Posted By Alex Fisher on Monday, December 7, 2009 - 16:28

When I originally set out to create a form with dynamic select boxes I thought it'd be easy enough to just create some fields, throw some values into the fields, and then repeatedly modify the options on one field based on the option selected in another field. While Drupal is built around forms (and provides many ways for creating and changing them) the best way for handling dynamic select boxes populated from an external database wasn't obvious.

I evaluated and tested all sorts of ways for creating forms using modules like CCK and Webform, creating my own forms with a custom module, and a combination of both. In the end, I figured-out an elegant solution for creating a 300+ field multi-page form with 35 dynamic select boxes and AJAX database queries for over 44,000 different vehicles.

Solution Overview

This article explains how to create dynamic drupal select boxes in 3 main steps:

  1. Building your form with the Webform Module
  2. Creating a custom module to access your database
  3. Dynamically creating and changing select boxes on your form

At the end of these 3 steps you'll be a wizard at using Drupal to create forms, create modules, create web services, and more. Well, maybe not a wizard, but hopefully it leads you in the right direction so you can become the wizard you need to be instead of spending days and weeks like I did.

Step 1: Building your form with the Webform Module

I settled on using the Webform Module since it's designed for creating multi-page forms with all types of fields quickly. Install this module if you don't have it already. It has a huge community of users and seems really well supported by the module's maintainer quicksketch.

It's possible to create forms using regular Drupal nodes with CCK if your form is very simple, but you'll probably like a lot of the features that are done for you with the Webform Module (e.g. detecting/providing limits for the number of submissions by a user, reviewing submission statistics, built-in field types, etc.).

This is the easiest step and you'll probably understand how to use the webform module after just taking a few minutes to create one and play with it. For many users, the module in its original form will be powerful enough for many of their needs. I wasn't lucky enough for that, and neither are you if you're still hoping to do dynamic selects, so read on!

Step 2: Creating a custom module to access your database

This step assumes you have a set of data you need to access when you're populating your select boxes. If you have a small amount of data you might be better off including it as static data in a variable or object in the next step. Otherwise, read on for how to build a module capable of accessing a database and providing data via a URL with parameters.

There are already other, much better, resources and tutorials for creating Drupal Modules. I would recommend spending a little time creating your own bare-bones module from one of these tutorials to familiarize yourself before continuing on.

The Drupal menu system provides a method for executing your own code when specific URLs are requested from your Drupal website. The name "menu system" originally confused me as I pictured menu system being a menu bar or set of links on a web page. In reality, the menu system provides functions to create menus like theses, but also allows a user to access a URL such as http://www.mysite.com/parameter1/parameter2/parameter3, execute a function called mymodule_menu(), and return any sort of data you want.

The data you return could be another page on your drupal site, a file, or in this case a JSON object containing the options for a select box.

Using hook_menu()

Add something like the following code to your module file (this example is for a module called mymodule so we would edit the file at /sites/all/modules/mymodule/mymodule.module ):

  1. /<em>
  2. </em> hook_menu() implementation example for module called "mymodule"
  3. */
  4. function mymodule_menu() {
  5.   $items = array();
  6.  
  7.   if (!$may_cache) {
  8.   // JSON data feed for AJAX calls
  9.     $items['data/json'] = array(
  10.       'page callback' => '_mymodule_json',
  11.       'type' => MENU_CALLBACK,
  12.       'access arguments' => array('access content'),
  13.     );
  14.   } 
  15.  return $items;
  16. }

This code means whenever a user (or an AJAX request from a form) calls the URL http://www.mysite.com/data/json it would receive data from the function _mymodule_json()! What's cool, is you can extend this even further by passing parameters to the URL so your function can return different data based on those parameters.

Returning JSON Data

Add the following code to your module so it returns a JSON object when accessed:

  1. /**
  2.  * Menu handler.
  3.  *
  4.  * Example URL: http://www.mysite.com/data/json?op=getYears
  5.  * Example URL: http://www.mysite.com/data/json?op=getMakes&value=2003
  6.  * Example URL: http://www.mysite.com/data/json?op=getModels&value=Ford
  7.  */
  8. function _mymodule_json() {
  9.   $op = $_GET['op'];
  10.   $value = $_GET['value'];
  11.   $data = NULL;
  12.  
  13.   // Make sure we have valid parameters
  14.   if( $op == NULL ) {
  15.     print 'Error, no operation specified' . '<br />';
  16.     return;
  17.   }
  18.  
  19.    // Look-up data based on OP and VALUE parameters
  20.   switch($op) {
  21.     case 'getYears':
  22.       $data = _mymodule_get_years();
  23.       break;
  24.     case 'getMakes':
  25.       $data = _mymodule_get_makes($value);
  26.       break;
  27.     case 'getModels':
  28.       $data = _mymodule_get_models($value);
  29.       break;
  30.   }
  31.  
  32.   print json_encode($data);
  33. }

Please note: there are a couple different ways to handle parameters in your URL and this is just one example.

Pulling Data From A Database

Now, you could place code in each of the case statements above to access your database and return an array of data to encode into a JSON object, but I've separated this out into different functions for style and readability. An example for _mymodule_get_years() is:

  1. /**
  2.  * Pull a list of years from the database.
  3.  */
  4. function _mymodule_get_years() {
  5.   $year = Array();
  6.  
  7.   // SELECT DISTINCT Year FROM <code>table</code> ORDER BY Year
  8.   $result = db_query( 'SELECT DISTINCT Year ' .
  9.                       'FROM {table} ' .
  10.                       'ORDER BY Year');
  11.  
  12.   while ($row = db_fetch_array($result)) {
  13.     $year[$row['Year']] = $row['Year'];
  14.   }
  15.  
  16.   return $year;
  17. }

The function above would return an array of years (all the years of vehicles in your database table) which in turn would be returned by your_mymodule_json() function.

For example, let's look at a URL and what it would return if you looked at it in your browser.

The URL http://www.mysite.com/data/json?op=getYears will return the following:

{"2000":"2000","2001":"2001","2002":"2002","2003":"2003","2004":"2004",
"2005":"2005","2006":"2006","2007":"2007","2008":"2008","2009":"2009",
"2010":"2010"}

If you're able to see the data come back to you in your web browser then you're ready to move on to the next step-- integrating it into select boxes on your form.

Step 3: Dynamically creating and changing select boxes on your form

Now, the reason why we're here-- dynamic select boxes. First, some background on Drupal's form system and its security features.

Background on Drupal's Form System

Drupal's form system consists of the programming to display, submit, validate, and process forms on Drupal-based sites. An important feature of forms is ensuring the data submitted on the form is safe. By safe, I mean data that won't destroy your site or database when submitted. Drupal helps protect against attacks to your site by ensuring any form that has options, in a select box for example, submit an option that matches one of the options known at the time the form was created for display.

This creates a major problem when a site creator displays a form and then using javascript, PHP, or some other language modifies the form after Drupal has prepared and displayed it on the site. What happens if you try this? Drupal gives you a message after submitting the form indicating the field(s) have an illegal choice and refuses to process the form submission.

Don't Use DANGEROUS_SKIP_CHECK

In Drupal 5, when I originally created this form, there was a great hack (or not so great... many developers called it an "evil" hack) that allowed you to modify the form in your custom drupal module setting a variable called DANGEROUS_SKIP_CHECK to false. That's a perfectly insecure, but do-ableway in Drupal until you decide to upgrade your view on security importance and upgrade your site to Drupal 6 where you cannot use DANGEROUS_SKIP_CHECK to submit your modified select box options! So, how do we create dynamic select boxes with options we cannot statically add to the form (e.g. 44,000 different vehicle types that could be updated or changed everyday), but adhere to Drupal's seemingly paradoxical form rules of not modifying select box options?

The Answer To The Drupal Form Paradox

The solution is to create a textfield on your form to pair with each dynamic select box you'll need, create select boxes in JavaScript after Drupal has generated the form, store select box options in the textfields when the user clicks the option, and then submit. Drupal knows textfields will contain information that isn't pre-determined before the form is displayed so it won't generate a security warning and will ignore any additional fields you added to the form (like dynamic select boxes) since it didn't know it needed to store them in the first place!

Adding JQuery/PHP Markup Code To Your Form

Follow these steps to add code to your form:

  1. First, edit your form and go the list of Form Components. The Webform Module has a field type called markupand you'll need to add one to your form.
  2. Next, you'll need to add JavaScript. I prefer to store JavaScript in a separate file so I can use an external editor instead of coding directly on the site. To do this, be sure you select an Input Format that allows the script tag such as Full HTML and insert:

  1. <script type="text/javascript" src="/sites/all/modules/mymodule
  2. /jquery.selectboxes.js"></script>
  3. <script type="text/javascript" src="/sites/all/modules/mymodule/
  4. form.js"></script>

You'll then have to create a file called form.js containing JQuery code to create the select boxes, enable/disable boxes, watch for click events, retrieve option data from your AJAX service, and store selections in the form's respective textfields. This file is somewhat large so view/download a copy. There is also a helpful utility for dealing with select boxes in JQuery I make use of in form.js. You can download a copy of jquery.selectboxes.js from TextoTela.

How Does All This JQuery Code Work?

At this point, you're finished and have completed the steps necessary to handle the dynamic select boxes. The magic is really performed in form.js so I'll try to explain the logical structure of the file and hopefully the inline comments will fill-in the rest:

  • Initialization
    • Dynamic select boxes are inserted into the page before each of the textfields created in the webform
    • The first select box is populated with options using JQuery's AJAX functionality and the web service you created in Step 2 above
    • Select boxes that will be populated with different options depending on the options selected in other select boxes are disabled
  • Bind event handler functions to run when the user chooses an option in a select box (note: use the change event instead of click due to issues with IE6)
    • These handler functions will validate the selection (to ensure it's valid) and will populate the next select box with new options based on the option in the current select box
    • It's also important to enable/disable select boxes so the user can only interact with valid, populated select boxes

Conclusion

Wow, what a lot work for a couple of select boxes! Hopefully, the explanation above makes sense and helps you enable some dynamic functionality on your Drupal-based site. Some of the steps can be performed in different or alternate ways, but the important piece of information to take out of this is understanding you can't just modify a select box created by Drupal, but you can create your own select boxes which store data into Drupal-created text fields!