Monthly Archives: April 2012

3 Smart PHP-Generated Drop Down Menus (Tutorial)

In our software we often use drop-down menus. To make things easier I have coded functions for the most commonly used ones – date drop down, drop down to list database entries, and drop down to be populated from simple non numerated PHP array. All these drop downs are for example used in our autoresponder software. They just generate HTML code which is then output in your HTML forms.

Just recently I decided to encapsulate them in a class and share with the community. So if you are lazy to read, go right to the code at PHP Classes. The class is called QuickDD.

For the more curious people who want to learn, here is this tutorial explaining the stuff.

The Class

Using class isn’t that important in our case because there is almost no shared data between the 3 drop down generating functions (except the errors array). But we’ll create a class so we can keep it encapsulated at least:

class QuickDD
{
    // this variable will be filled with errors. 
    // You can print it out with print_r so you can see errors
    public static $errors=array();

    // used by the date drop down. Feel free to change start/end year
    public static $start_year=1900;
    public static $end_year=2100;

You can very well just use three procedural functions. But from now on, let’s stick with the OO example.

The Date Drop-Down

This is the most complex one. I know there are many cool scripts like jQuery calendar but sometimes they are a bit painful to work with. Simple drop-down date selection is easy to understand, works in all browser, and takes no javascript to implement. So, let’s build one:

    // Outputs date drop down for selecting month, day, year 
    // Arguments:
    // @param $name - string, the base name of the "select" form tag.  
    // Quick DD will add suffix to each of the 3 tags:
    // "year" for year, "month" for month, and "day" for day
    // @param $date - string in MySQL format, optional pre-selected date
    // @param $format - string in the type YYYY-MM-DD showing how to output the 
    // drop-downs. If $format has anything insane (like 5 Y or single-digit month)
    // we'll quietly switch to something sane instead of raising exception.
    // but we may log an error
    // @param $markup - if presented it should contain any markups that will be added
    // to each dropdown, in the same order that the parts are displayed.

And the function starts this way:

static function date($name, $date=NULL, $format=NULL, $markup=NULL)
    {

It’s static because we wan to call it anywhere without creating object. When you just want to encapsulate methods in a class for organization reasons, and there is no class instance data, it’s better to use static methods.

At the beginning of the function we want to make sure the params passed are so-so sane:

        // normalize params
        if(empty($date) or !preg_match("/\d\d\d\d\-\d\d-\d\d/",$date)) $date=date("Y-m-d");
        if(empty($format)) $format="YYYY-MM-DD";
        if(empty($markup)) $markup=array();

Now let’s explode the optionally passed pre-selected date, and start to buffer the output of the method:

$parts=explode("-",$date);
$html="";

// read the format
$format_parts=explode("-",$format);

And in $format_parts now we have the format as requested when calling the method. Now the most important part starts:

        // let's output
        foreach($format_parts as $cnt=>$f)
        {
            if(preg_match("/[^YMD]/",$f)) 
            { 
                self::$errors[]="Unrecognized format part: '$f'. Skipped.";
                continue;
            }

As you see we go through each parts of the format string and check whether it contains anything else than the allowed symbols Y, M, and D. If yes, we add to errors array and skip that part. Errors array can be output with print_r when you use the method to see whether there are errors (this will become more clear in the usage example that will come after the code.

Now let’s add the year drop-down:

            // year
            if(strstr($f,"Y"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."year\"".$extra_html.">\n";

                for($i=self::$start_year;$i<=self::$end_year;$i++)
                {
                    $selected="";
                    if(!empty($parts[0]) and $parts[0]==$i) $selected=" selected";
                    
                    $val=$i;
                    // in case only two digits are passed we have to strip $val for displaying
                    // it's either 4 or 2, everything else is ignored
                    if(strlen($f)<=2) $val=substr($val,2);        
                    
                    $html.="<option value='$i'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

Few things to note: $extra_html is populated by the element of the $markup argument which corresponds to the position of where YYYY (or YY) is in the format. So if you pass format like "DD-MM-YY" the third element of the array argument $markup will be used. If empty, nothing is used.

Then see how we prepend "year" to the $name argument to create the name of the drop-down. So each drop-down has its own name. For example if you call the method with $name equal to "date" your 3 drop downs will have names "dateyear", "datemonth", and "dateday" and can be accessed in $_POST or $_GET with these names.

Next we go through start and end year in a loop and output the option tags. In case $date argument is passed, the $parts value will contain year in $parts[0] (Because $date argument should always be passed in the MySQL YYYY-MM-DD format). So we check if such value is passed and when it matches $i we set the var $selected so the option tag can have selected attribute.

Finally see how we check if the format should be YYYY or YY and substring the output to meet that.

Then comes month drop down which is nearly the same:

            // month
            if(strstr($f,"M"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."month\"".$extra_html.">\n";

                for($i=1;$i<=12;$i++)
                {
                    $selected="";
                    if(!empty($parts[1]) and intval($parts[1])==$i) $selected=" selected";
                    
                    $val=sprintf("%02d",$i);
                        
                    $html.="<option value='$val'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

The thing to notice here is the usage of sprintf which makes sure leading zero is always shown.

And now, day:

          // day - we simply display 1-31 here, no extra intelligence depending on month
            if(strstr($f,"D"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."day\"".$extra_html.">\n";

                for($i=1;$i<=31;$i++)
                {
                    $selected="";
                    if(!empty($parts[2]) and intval($parts[2])==$i) $selected=" selected";
                    
                    if(strlen($f)>1) $val=sprintf("%02d",$i);
                    else $val=$i;
                        
                    $html.="<option value='$val'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

Hopefully you noticed how looping through format parts first let's us position the dropdown in the same order as given in the $format parameter.

So essentially that's it, we can now output the $html:

       // that's it, return dropdowns:
        return $html;
    }

Again, the full source code is available at PHP Classes so you can see it there as a whole.

Using the Date Drop Down:

Here is how to call this now:

    QuickDD::$start_year=2009;
    QuickDD::$end_year=date("Y")+10;

    // see how we pass the base name for the dropdowns, values, and markup array which will
    // set DOM ID, CSS class, and Javascript callback to every drop-down. 
    // Of course all this is optional
    echo QuickDD::date("final", "2012-12-21", "DD-MM-YYYY", array(" id='selectDay' class='some_class' onchange='doSomething(this.value);' "," id='selectMonth' class='some_class' onchange='doSomething(this.value);' ", " id='selectYear' class='some_class' onchange='doSomething(this.value);' "));    
    
    if(!empty(QuickDD::$errors)) print_r(QuickDD::$errors);

This way you can add all kind of javascript and CSS markup, pre-selected values etc. But of course it can be called also in much simpler way using the defaults:

QuickDD::date("date");

Really simple.

Drop-down With Associative Arrays

These are very often used in DB driven apps. In our example we'll have a simple DB table with students where each student has an ID, email, and name. And we want to display a drop down menu to display a student. Visually the student name will be shown, but the value attribute will show the student ID so we can process it further in the app.

Here's the beginning:

// Outputs drop-down populated from associative array (usually coming from database)
    // @param $rows - the associative array
    // @param $value_field - the name of the key from the array whose value will be placed into
    // the option tag "value" attribue
    // @param $display_field - the name of the array key that will be used for display text
    // @param $sel - optionally a pre-selected value (for example when editing DB record)
    public static function hash($rows, $value_field, $display_field, $sel=NULL)
    {

Now the main function code is just few lines:

$html="";
        foreach($rows as $cnt=>$row)
        {
            if(is_array($sel))
            {           
                // multiple-select drop-down
                if(@in_array($row[$value_field],$sel)) $selected=' selected';
                else $selected='';
            }
            else
            {
                // single-select drop-down
                if($sel==$row[$value_field]) $selected=' selected';
                else $selected='';
            }
            $html.="<option value=\"$row[$value_field]\"".$selected.">$row[$display_field]</option>\n";
        }
        return $html;

What we do here:
1. We start with $html buffer
2. Then loop through the DB records
3. Check if pre-selected value is passed, whether it's array of values (useful in multiselect drop downs), and check if it matches the current row.
4. Output the display field and value fields appropriately.

That's it. And here's how to use this method:

<select name="student_id">
    <option value="">- Please select -</option>
    <?php echo QuickDD::hash($students, "id", "name", 2);?>
    </select>

Drop-down With Simple Arrays

Simple arrays means ones that are not associative although you can use associative too. The function will not look for keys though, it will work only with values. So it's good for list of colors (like in this example), numbers, names, currencies etc. The function accepts one array for displays, and one for value attributes of option tags. But more often you'd probably call it with the same array in both places so what you see is what you get as option value. Here's the function, then the example will help you understand better:

 // outputs dropdowns using simple arrays for value attribute and for display
    // @param $values - the array of values that goes to "value" attribute
    // @param $displays - the array of texts that will be displayed in the options
    // @param $sel - pre-selected value
    public static function arrays($values, $displays, $sel=NULL)
    {
        $html="";
        foreach($values as $cnt=>$value)
        {
            if(is_array($sel))
            {
                // for multiple-select drop-down
                if(in_array($value,$sel)) $selected=' selected';
                else $selected='';
            }
            else
            {
                // single-select drop-down
                if($sel==$value) $selected=' selected';
                else $selected='';
            }
            $html.="<option value=\"$value\"".$selected.">$displays[$cnt]</option>\n";
        }
        
        return $html;
    }

It's very similar to the previous one. But instead of getting values by keys, we browse through each array.

Here's example usage with colors:

<p>Select color: <?php
    // here's the color array
    $colors=array("black","white","blue","green","red","orange","yellow");
    ?>
    <select name="color">
    <option value="">- Please select -</option>
    <?php echo QuickDD::arrays($colors, $colors);?>
    </select></p>

That's it. You can improve the class by adding more error checking and some friendlier way to display the errors (for example in HTML comments).

Do you have ideas for other often used drop-downs that can be added to the class?