picture of Dan LaFlamme

Introduction

Checkboxes in web forms are useful because they allow the person filling out the form to choose from a wide range of possible choices. Specifically, it allows the user to choose some, all or none of the elements. Although checkboxes are excellent form elements for certain tasks, there is often confusion about how to store checkbox data correctly. This article will demonstrate how to store checkbox data in a database, in a manner that follows good database design principles.

Requirements

In this article, I will demonstrate the method of storing checkbox data on a per user basis. Although there is useable PHP source code included, I'm presenting this in a database design point of view. Therefore, the information here can be used with any database and server side scripting language of choice. The main idea here is to provide you with an explanation of how to do it so you can apply it to the sites you build. If you do want to play with the source code, you'll need php, mysql, and a webserver.

Example: A Job Posting Site

Consider the following situation. You are asked to create a web site that will allow unemployed web developers to post their skills there. Potential employers can then visit this website and search for prospective employees, based on the skills they are looking for. As an aspiring web developer yourself, you know that a web programmer with work is more desirable than one without. Therefore, you decide to write the code for his site.
Each unemployed web developer will presumably visit the site, create an account, and enter his skills in some way or another. Checkboxes immediately come to mind. You envision a page that looks something like this:
  __ PHP     __ MySQL        __ Zope
  __ Perl    __ Javascript   __ JSP

                          [Submit]
Each developer goes through and checks the boxes that match his skills. Obviously, different developers will check different boxes. One may check PHP and MySQL, while another checks only JSP. How will you store this data? It is natural to want to store each developer's list of skills in the user table shown above. After all, each user will have a row in this table and you can just make some fields in each row to hold their skills. This may work at first, but you will almost surely run into trouble at some point when you wish to expand or modify the database. Also, your user table would be unnecessarily wide. There is a better way to do this. Here is how.
We'll have a user table that contains user login information such as username, password, and some other fields that we might use. A simple user table is shown below. I will assume you can create a table like this if you plan to use some of the example code found later in the article.
id username
1 User1
2 User2
3 User3
We first create a table called "const_skills" with the following SQL code:
SQL> CREATE TABLE const_skills ( 
	id     int  not null primary key, 	 
	value  varchar(20) );
And now we will populate it with skills:
SQL> INSERT INTO const_skills(id, value) VALUES (1, "PHP");
SQL> INSERT INTO const_skills(id, value) VALUES (2, "MySQL");
SQL> INSERT INTO const_skills(id, value) VALUES (3, "Zope");
SQL> INSERT INTO const_skills(id, value) VALUES (4, "Perl");
SQL> INSERT INTO const_skills(id, value) VALUES (5, "Javascript");
SQL> INSERT INTO const_skills(id, value) VALUES (6, "JSP");
Your const_skills table now looks like this:
id value
1 PHP
2 MySQL
3 Zope
4 Perl
5 Javascript
6 JSP
This table's only purpose is to hold the names of the skills that web developers can select. Now, create a table called "lookup_skills" with the following SQL:
SQL> CREATE TABLE lookup_skills ( 
	id        int  not null auto_increment primary key,
	uid       int,
	skill_id  int );
The purpose of lookup_skills is to provide a mapping from userids to web devloper skills. In other words, it allows us to store the developers and what skills they have. So, when the unemployed web developer hits the submit button after filling out our form, we will populate this table with the checkbox values that were checked off. For each skill the developer checks off, a row will be added with the user's uid from the user table and the number corresponding to the skill he selected in the const_skills table. Before we look at the code to do the insert, lets first use our design to make creating our form easier. Each time that the user requests the page that has our form on it, we can query the database and get the checkbox labels from the const_skills table and create the checkbox form with the appropriate checkbox choices. Here is the code to do that.

<?php

  
/* insert code to connect to your database here */  
  
  /* get the checkbox labels */
  
$skills get_checkbox_labels("const_skills");
  
  
/* create the html code for a formatted set of
     checkboxes */
  
$html_skills make_checkbox_html($skills3400"skills[]");

?>
<html>
<body>
<br>
<form name="skills" method="POST" action="insertskills.php">
   Check off your web development skills:
<? echo "$html_skills"; ?> <br> <input type="submit" value="Submit"> </form> </body> </html>
<?php

function get_checkbox_labels($table_name) {

  
/* make an array */
  
$arr = array();
  
  
/* construct the query */
  
$query "SELECT * FROM $table_name";
  
  
/* execute the query */
  
$qid mysql_query($query);

  
/* each row in the result set will be packaged as
     an object and put in an array */
  
while($rowmysql_fetch_object($qid)) {
    
array_push($arr$row);
  }
  
  return 
$arr;
}

/* Prints a nicely formatted table of checkbox choices. 
   
   $arr is an array of objects that contain the choices
   $num is the number of elements wide we display in the table 
   $width is the value of the width parameter to the table tag
   $name is the name of the checkbox array 
   $checked is an array of element names that should be checked
*/   


function make_checkbox_html($arr$num$width$name$checked) {
  
  
/* create string to hold out html */
  
$str "";
  
  
/* make it */
  
$str .= "<table width=\"$width\" border=\"0\">\n";
  
$str .= "<tr>\n";

  
/* determine if we will have to close add
     a closing tr tag at the end of our table */
  
if (count($arr) % $num != 0) {
    
$closingTR true;
  }
  
  
$i 1;
  if (isset(
$checked)) {
    
/* if we passed in an array of the checkboxes we want 
       to be displayed as checked */ 
    
foreach ($arr as $ele) {
      
$str .= "<td><input type=\"checkbox\" name=\"$name\" value=\"$ele->id\"";
      foreach (
$checked as $entry) {
    if (
$entry == $ele->value) { 
      
$str .= "checked";
          continue;
        }
      }
      
$str .= ">";
      
$str .= "$ele->value";

      if (
$i $num == 0) {
        
$str .= "</tr>\n<tr>";
      } else {
        
$str .= "</td>\n";
      }
      
$i++;
    }
  
  } else {
    
/* we just want to print the checkboxes. none will have checks */
    
foreach ($arr as $ele) {
      
$str .= "<td><input type=\"checkbox\" name=\"$name\" value=\"$ele->id\">";
      
$str .= "$ele->value";
      
      if (
$i $num == 0) {
        
$str .= "</tr>\n<tr>";
      } else {
        
$str .= "</td>\n";
      }
      
$i++;
    }
  
  } 

  
/* tack on a closing tr tag if necessary */
  
if ($closingTR == true) {
    
$str .= "</tr></table>\n";
  } else {
    
$str .= "</table>\n";
  }

  return 
$str;
}


?>
That was quite a bit of code, so here is a quick overview of what is going on. The majority of the work is done in two functions, get_checkbox_labels and make_checkbox_html. get_checkbox_labels queries our const_skills table and returns an array of objects, each object having an id and the name of the skill that corresponds with that number. We then pass this array to make_checkbox_html along with a few other parameters and it returns a string with the html code for the checkboxes. We now just insert the string into our html form code to include the skill checkboxes in our form. Notice that I didn't pass the $checked argument to make_checkbox_html. This argument is an array of the objects we want to appear checked. If a user learned a new web development skill after filling out the form for the first time, we could provide an "edit skills" page that would present the checkboxes with his previously stored skills already checked off.
What is the benefit of creating the form in this way instead of simply hard coding the skill choices in the html code of our form? Well, suppose we want to allow the web developers to be able to choose a skill that we don't already have in our const_skills table, DHTML for instance. All we would need to do is insert a row for DHTML into the const_skills table. Then, when the developers go to the form page, DHTML will be there. No modifications to the html form code is necessary.

Inserting into lookup_skills

Now that we have the code to create the form, we need to be able to store the skills that the user checks off. In the make_checkbox_html function above, we called the name of each checkbox element skills[]. The "[]" specify that we will get access to the checked elements in an array. So, all we have to do is run through the array and insert rows into the lookup_skills table. If the person filling out the form clicks 5 skills, we insert 5 rows into the lookup_skills table. Remember that each row in the lookup_skills table has a user id number and a skill number. In the example of the site I gave, users log in and then they can create/edit their profile. You would probably register their userid number as a session variable when they log in, but how you manage userids is beyond the scope of this article. In the code below, we will just assume that we have access to the userid in a variable called $uid. So here are some functions to do the insert:

<?php

/* the function we call to insert.
   the $skills argument is the skills array that
   is sent to the script when the user hits the submit button
 */
function insert_skills($uid$skills) {
   
   
/* first, we'll delete any entries this user already has
      in the table */
   
purge_lookup("lookup_skills"$uid);
   
   
/* now create the sql insert query */
   
$query create_checkbox_query($skills"lookup_skills"$uid);
   
   
/* execute the query */
   
mysql_query($query);
}

/* helper function for insert_skills().
   removes all rows in $table with $uid */
function purge_lookup($table$uid) {
  
$q "DELETE FROM $table, WHERE uid = '$uid'";
  
mysql_query($q);
}

/* helper function for insert_skills(). 
   generates the sctual SQL query */
function create_checkbox_query($arr$table$uid) {
   
$q "INSERT INTO $table (uid, skill_id) VALUES";
  
   foreach (
$arr as $check) {
     
$q .=  " ( $uid , $check )" ",";
   }
   
   
/* remove the last comma and return */  
   
return substr($q0, -1);
}

?>
That is pretty much all there is to inserting. You now know how to create your form easily by reading from your const_skills table and you also know how to store the users skill selections by simply inserting rows into the lookup_skills table. So now what do you do? Lets look at how to search.

Searching

When someone looking to hire web developers comes along and goes to your search page, you can show the same form and allow that person to check the qualifications he is looking for. You'll get the array of his choices, then you can just loop through that array, building a query to select web developers with those qualifications. You can then display a list or results and allow the searcher to click on an entry in the list to see a more detailed profile. Here is a function demonstrating how to build the query:

<?php

/* builds a query to search for the skills
   checked off in the $skills array */

function skill_search($skills) {
   if (!empty(
$skills)) {
     
$query "SELECT DISTINCT user.username
                 FROM user, const_skills, lookup_skills
                WHERE lookup_skills.uid = user.id  
                  AND lookup_skills.skill_id = const_skills.id "
;

     
$query .= " AND (";
     foreach (
$skills as $check) {
       
$query .= " const_skills.id = $check OR";
     }

     
/* remove the final OR */
     
$query substr($query0, -2);
     
$query .= ")";
   
     
$count count($skills);
     
$query .= " GROUP BY user.username HAVING count(user.username) >= $count";
    
     
$query .= ";";
     return 
$query;
   }
}

?>
If the person performing the search checked "PHP" and "Javascript", this function would return the query:
SELECT DISTINCT user.username FROM user, const_skills, lookup_skills 
 WHERE lookup_skills.uid = user.id AND lookup_skills.skill_id = const_skills.id 
   AND ( const_skills.id = 3 OR const_skills.id = 5 ) 
 GROUP BY user.username HAVING count(user.username) >= 2; 
Note that this function returns the logical "AND" of the boxes you check. That is, if both PHP and Javascript are checked as in the above example, we will only get usernames of those web developers who know *BOTH* PHP and Javascript. If you wanted the usernames of those who know either PHP *OR* Javascript (or both), everything would be the same except that you would remove the line that appends the SQL code "GROUP BY..." to the query.

Conclusion

Well, that's it for this article. Checkboxes are excellent for tasks like the one mentioned here and I hope this helps people wondering how to work with them on their data driven web sites.
-- Dan