Multiple Select Fields

We humans are greedy creatures. When we're placed in front of a choice to select one of many options, we start complaining — Hey, why can't I pick two? Why not five? Can't I have both pineapple AND pepperoni on my pizza?

This is why multiple selection form fields had to be invented. Let's examine a few examples of how multiple select fields can be implemented in HTML and PHP (as well as some JavaScript).

Traditional HTML/CSS techniques

Checkboxes

The first obvious option that we have is the good old set of checkboxes:

What do you like on your pizza?




The HTML code behind this is pretty straightforward:

<input id="cheese" type="checkbox" name="ingredients[]" value="Cheese" /> <label for="cheese">Cheese</label> <br /> <input id="olives" type="checkbox" name="ingredients[]" value="Olives" /> <label for="olives">Olives</label> <br /> <input id="pepperoni" type="checkbox" name="ingredients[]" value="Pepperoni" /> <label for="pepperoni">Pepperoni</label> <br /> <input id="anchovies" type="checkbox" name="ingredients[]" value="Anchovies" /> <label for="anchovies">Anchovies</label>

You might notice that all the checkboxes share the same name ingredients[]. The square brackets allow us to easily parse the collected form data in PHP. We'll get to that later.

Lists of checkboxes have the advantage of being simple to implement and easy to use for the end user. But, they do not scale well — as long as there are just a few options, it's fine, but if you need to let the user select a few choices from dozens of options (a list of countries, for instance), you end up with a really, reeeally long list, and your form gets huge. And if there's one thing people hate, it's huge forms.

There are some workarounds for long lists of checkboxes, like placing the checkboxes in several columns:







This can be accomplished with floating divs:

<div class="container"> <div style="width: 12em; float: left;"> <input id="cheese" type="checkbox" name="ingredients[]" value="Cheese" /> ... </div> <div style="width: 12em; float: left;"> <input id="mushrooms" type="checkbox" name="ingredients[]" value="Mushrooms" /> ... </div> </div>

Another workaround is to put all the checkboxes in a scrollable div:








For this variant, all we need to do is give the div some dimensions and set its CSS overflow property to auto:

<div style="height: 6em; width: 12em; overflow: auto;"> <input id="cheese" type="checkbox" name="ingredients[]" value="Cheese" /> <label for="cheese">Cheese</label> <br /> ... </div>

With the scrollable list, we can keep the form small even with a hundred options. But (there's always a but), this solution has one major drawback — the user can't see all the already checked options at once without scrolling the box.

Multiple selection list box

There is a HTML element specifically designed for multiple selection. The select element, usually used to produce a drop down list, can be transformed into a multiple selection box with the addition of the multiple attribute:

Here's the code:

<select name="ingredients[]" multiple="multiple" size="5"> <option value="Cheese">Cheese</option> <option value="Olives">Olives</option> <option value="Pepperoni">Pepperoni</option> ... </select>

Apparently, this method suffers from the same problem as the scrollable checkbox list — you need to scroll the box to see all your choices. Oh well.

Fancy JavaScript method

In light of the above, the optimal solution should be both compact and let us see all the selected options without scrolling. We can't accomplish this with just HTML and CSS, but, hey, we have JavaScript, right?

Let's use our imagination here. To keep the list of options condensed, we can use a drop down box. Everytime the user selects an option, we'll do some JavaScript-dynamic-HTML work to add the selected option to a list placed above the box. This way, all our choices will be visible at the same time.

Can you picture it? If not, don't worry, here's a working example:

  • Cheese
  • Ham
  • Mushrooms

We're also able to remove the already selected options by clicking them on the list. Oh, and one more thing — when adding an option, we make sure it's not already listed (so that we don't end up with too much bacon on our pizza).

With some CSS styling and an image here and there, our solution can get a more attractive look:

  • Cheese
  • Ham
  • Mushrooms
Add

Nice, isn't it? Here's the HTML:

<ul> <li onclick="this.parentNode.removeChild(this);"> <input type="hidden" name="ingredients[]" value="Cheese" /> Cheese </li> <li onclick="this.parentNode.removeChild(this);"> <input type="hidden" name="ingredients[]" value="Ham" /> Ham </li> <li onclick="this.parentNode.removeChild(this);"> <input type="hidden" name="ingredients[]" value="Mushrooms" /> Mushrooms </li> </ul> <select onchange="selectIngredient(this);"> <option value="Cheese">Cheese</option> <option value="Olives">Olives</option> <option value="Pepperoni">Pepperoni</option> ... </select>

The structure here is quite simple — an unordered list and a drop down select field. One thing worth noting is how we store the selected values to have them sent to the server when the form is submitted. For this purpose, inside every list item there's a hidden input element with the appropriate value.

All the action happens in the JavaScript function selectIngredient:

function selectIngredient(select) { var option = select.options[select.selectedIndex]; var ul = select.parentNode.getElementsByTagName('ul')[0]; var choices = ul.getElementsByTagName('input'); for (var i = 0; i < choices.length; i++) if (choices[i].value == option.value) return; var li = document.createElement('li'); var input = document.createElement('input'); var text = document.createTextNode(option.firstChild.data); input.type = 'hidden'; input.name = 'ingredients[]'; input.value = option.value; li.appendChild(input); li.appendChild(text); li.setAttribute('onclick', 'this.parentNode.removeChild(this);'); ul.appendChild(li); }

If you don't like dealing directly with DOM, here's a jQuery equivalent:

function selectIngredient(select) { var $ul = $(select).prev('ul'); if ($ul.find('input[value=' + $(select).val() + ']').length == 0) $ul.append('<li onclick="$(this).remove();">' + '<input type="hidden" name="ingredients[]" value="' + $(select).val() + '" /> ' + $(select).find('option[selected]').text() + '</li>'); }

The function checks if the selected option is not already on the list — it does that by looking for a hidden input element with the selected value. If it fails to find one, it creates a new list item for the chosen option (with a new hidden input inside) and appends it to the list.

Note that the hidden input elements are named ingredients[], just like the checkboxes and the multiple selection list box in the previous examples. This will allow us to use the same PHP code to deal with the submitted data.

Processing results

As I mentioned, using the square brackets in the input/select name makes it easier to process the collected form data in PHP. That's because it automatically produces an array for us to work with. You can read more about this in the PHP FAQ.

Assuming we get our data via $_POST, we can process each selected option with the following foreach loop:

foreach ($_POST['ingredients'] as $ingredient) { // do something with $ingredient }

The "do something" part might be whatever you want to do with $ingredient — store it in a database, save it in a file, or mail it to your local Domino's Pizza.

Generating options dynamically in PHP

In many (if not most) cases, we will want to automatically generate the list of options based on some data (eg. a set of records retrieved from a database, XML file, or some other source). To simplify, we'll assume the data is already collected in an array.

Basically, there might be two types of arrays that we'll be using to produce the options. The first type is when we're only interested in item names, so we have a non-associative array of strings:

$ingredients = array('Cheese', 'Olives', 'Pepperoni', ...);

The second is when there is some additional information attached to each item, for example a database record ID, and this information is what is really important to us.

$ingredients = array('3' => 'Cheese', '5' => 'Olives', '8' => 'Pepperoni', ...);

In both cases, we can generate the HTML code for the list of choices with a simple foreach loop. To produce a set of checkboxes from a non-associative array (the first type), we do the following:

<? foreach ($ingredients as $ingredient) { ?> <input id="<?= $ingredient ?>" type="checkbox" name="ingredients[]" value="<?= $ingredient ?>" /> <label for="<?= $ingredient ?>"><?= $ingredient ?></label> <br /> <? } ?>

And here's the equivalent loop for the associative variant:

<? foreach ($ingredients as $key => $ingredient) { ?> <input id="<?= $ingredient ?>" type="checkbox" name="ingredients[]" value="<?= $key ?>" /> <label for="<?= $ingredient ?>"><?= $ingredient ?></label> <br /> <? } ?>

A similar foreach loop could be used to produce a multiple selection list box, or a drop down list for our JavaScript solution.

Pre-selecting options

We might want to present the user with a set of default, already selected options — say, we notice that the vast majority of our pizza place customers want bacon and green pepper, so we decide to pre-select the two ingredients for them and make their lives easier. We'll put the default options in an array named $selected:

$selected = array('Bacon', 'Green Pepper');

But this is not the only reason why we might want to have some options already selected when the form is displayed. If the user submits a form, and we notice there's some invalid or missing information, we need to display the form again and ask the user to correct the errors. To save the user the frustration of having to enter everything again, we need to fill the form with all the information already provided — in the case of multiple select fields, we need to pre-select the submitted set of options.

If the form has been submitted, we fill $selected with the submitted options. Additionally, we check if there actually are any submitted options, as the user might have not selected anything. If that's the case, $_POST['ingredients'] won't even exist, and we need to make $selected an empty array:

if (isset($_POST['make_order'])) { if (isset($_POST['ingredients'])) // Submitted selection $selected = $_POST['ingredients']; else // Nothing selected $selected = array(); } else { // Default selection $selected = array('Bacon', 'Green Pepper'); }

We then proceed with generating the HTML code for the set of options (checkboxes again). For every item, we use the in_array() function to check if the value is present in the $selected array — if it is, we add a checked attribute to the checkbox. With the non-associative array of options, we use the following foreach loop:

<? foreach ($ingredients as $ingredient) { ?> <input id="<?= $ingredient ?>" type="checkbox" name="ingredients[]" value="<?= $ingredient ?>" <?= in_array($ingredient, $selected) ? 'selected="selected"' : '' ?> /><label for="<?= $ingredient ?>"><?= $ingredient ?></label> <br /> <? } ?>

With the associative array, the code is pretty much the same, except that we need to check if the key ($key) is in the $selected array, not the value ($ingredient):

<? foreach ($ingredients as $ingredient) { ?> <input id="<?= $ingredient ?>" type="checkbox" name="ingredients[]" value="<?= $key ?>" <?= in_array($key, $selected) ? 'selected="selected"' : '' ?> /><label for="<?= $ingredient ?>"><?= $ingredient ?></label> <br /> <? } ?>

We can generate a multiple selection list box in a similar fashion. Unfortunately, we'll have a slight problem with our pretty JavaScript method. We surely can generate a list of items with a foreach loop:

<ul> <? foreach ($ingredients as $ingredient) { if (in_array($ingredient, $selected)) { ?> <li onclick="this.parentNode.removeChild(this);"> <input type="hidden" name="ingredients[]" value="<?= $ingredient ?>" /> Cheese </li> <? } } ?> </ul> <select onchange="selectIngredient(this);"> ... </select>

However, the $selected array might be empty. This would lead to generating an empty <ul> element, which is not valid HTML. We're good people and we want our HTML to be valid, so we have to do something about it.

We'll modify our code to check if the $selected array is empty, and if it is, we'll simply not generate the unordered list at all:

<? if (count($selected) > 0) { ?> <ul> <? foreach ($ingredients as $ingredient) { if (in_array($ingredient, $selected)) { ?> <li onclick="this.parentNode.removeChild(this);"> <input type="hidden" name="ingredients[]" value="<?= $ingredient ?>" /> Cheese </li> <? } } ?> </ul> <? } ?> <select onchange="selectIngredient(this);"> ... </select>

But that's not all. If $selected is empty and there's no <ul>, our precious JavaScript code that adds new items to the list will no longer work (for the simple reason that there would be no list to add to). To fix this, we need to add a few lines of code to the selectIngredient() function to check if the list exists, and if it does not, create it on the fly.

function selectIngredient(select) { var option = select.options[select.selectedIndex]; var ul = select.parentNode.getElementsByTagName('ul')[0]; if (!ul) { ul = document.createElement("ul"); select.parentNode.insertBefore(ul, select); } var choices = ul.getElementsByTagName('input'); ... }

Now, we no longer need to fear the empty $selected array.

More to follow. Stay tuned.