Dynamic Menus with PHP, JavaScript, and MySQL

Steve Michel

One of my favorite Robert A. Heinlein stories is in his novel Time Enough For Love. It's the story of Slipstick Libby, the man who was too lazy to fail. Because he's so lazy, he's always coming up with invaluable inventions that save lots of time. From the number of hits you get on Slipstick Libby on a Google search, he's a popular character. If you remember that you are often lazy, and that your users are too, it can help motivate you to come up with better tools to help them do the job.

A big example is data entry, of course. Forcing users to enter a bunch of data can make the data collection more difficult, and make it harder to ensure that you have good data in the first place. I'll discuss an example here.

For the past couple years I've been riding my bike a lot. Not fast, or competitive, mostly casual, but still a fair amount. Suprisingly for me, it took me a long time to start logging my rides. I started when my riding partner Ken gave me a spreadsheet a friend of his had created to track the rides. I started using it a lot, and added a lot to it. Excel is a nice tool, but heck, I'm a database guy, so I moved it into MySQL with a web interface.While I was doing it, I used a nice trick to make data entry easier.

The database and form. The database is really simple. It has only four fields: Date, Ride, Distance, and Time. They're pretty self-explanatory; Ride is the place I rode to, and Time is in minutes. Here's the form:

Date:
Distance:
Time:
Ride:

Pretty simple, right? Yet even with a really simple form like this, there's a lot you can do to make it more usable. For starters, there's no reason not to include a default date in the date field. I do this in PHP with code like this in Date INPUT tag:

	<input type="text" name="date" value="<?php echo date('Y-m-d'); ?>" / >

This formats the date so it looks like "2004-07-26" the default MySQL date format. Providing the default of today's date is logical, since I usually log my rides right after I get home, and having the correct format in there makes it easier to change the default if I do it a day or two later.

Adding the Popup Menu. But since I take a limited number of different rides, it gets tedious entering the same ride over and over again, along with pretty much the same distances and times. Looking down the spreadsheet, I found a bunch of places where I had mistyped the same ride in different ways: for "Arlington," I had typed "arlintgon" and "alrington". So I thought I'd add a popup to the form, so that I could quickly choose from among my half dozen regular rides, and have default values entered for the distance and time.

In the header of the page, put in some JavaScript. First, to create an array of rides:

var rides = new Array();
rides[rides.length] = {ride: "Arlington", dist: "7.45", time: "45"};
rides[rides.length] = {ride: "Emeryville", dist: "11.1", time: "52"};

And so on, to list all the rides. Then a function to set the values in the form:

function copyVals(rideNum) {
 document.ride.ride.value = rides[rideNum].ride;
 document.ride.dist.value = rides[rideNum].dist;
 document.ride.time.value = rides[rideNum].time;
 } 

Another function builds a popup list of the rides:

function createPopup() {
 result = "";
 for (var i = 0; i < rides.length; i++) {
 	result += "<option value=\"" + i + "\">" + rides[i].ride + "</option>\n";
 }
 return(result);
}

To the form, I add a "choose a ride" popup item that calls copyVals. The popup is build by the createPopup function:

<select name="rides" onchange="copyVals(this.value);">
   <option>Select a ride</option>
   <script language="javascript">
   document.write(createPopup());
   </script>
 </select>

Pretty cool. Here's the form:

Choose a ride:
Date:
Distance:
Time:
Ride:

Give it a try, it makes data entry a lot easier! You can see this all by viewing the source for this page.

Using the Database But I'm none too excited about hard coding those rides into my JavaScript. If I'm going to be keeping everything in the database, I may as well use the database to help me come up with the popups. It's a simple query to pull from the database the different rides I do, along with average distances and times (distances will vary because of little changes I make in the route; times by that and how energetic I am). The code looks like this:

select ride, round(avg(distance),2) as 'dist', round(avg(time),2) as 'time'
from rides
group by ride

Then you can use PHP in the header of the page to have it construct the JavaScript rides array for you:

(Code to connect to the database, do the query, and put the results into a PHP named rides left out.)

var rides = new Array();
<?php while ($row_rides = mysql_fetch_assoc($rides)) { ?>
 rides[rides.length] = 
 	{ride: "<?php echo $row_rides['ride']; ?>",
 	dist: "<?php echo $row_rides['dist']; ?>", 
 	time: "<?php echo $row_rides['time']; ?>"
 };
 <?php }  ?>

Here, PHP is actually writing some JavaScript for you, creating each of the records in the array as it loops through the results from the database. A nice trick.

When I first tried it, I was none too happy. I had a number of one-off rides in the database, rides I had done only once and didn't need to enter again. So I modified the MySQL query to only include rides I had taken twice or more in the popup:

 	select ride, round(avg(distance),2) as 'dist', round(avg(time),2) as 'time' 
	from rides 
	group by ride
	having count(*) > 1

This took care of that! My popup list is now manageable and contains all the rides I'm likely to encounter during my day to day riding; entering new one-offs still isn't hard, and if I log a new ride more than once, it'll automatically appear in the popup.

This technique can be very useful. I've used it another instance, where I was building a sort of CRM tool, and companies would have multiple locations. When enter a company contact, the popup would automatically show the company's various locations, and fill in contact information such as address and fax number. It makes it easier for users to enter correct data, eliminating annoying dialog boxes on error checking, and bad data getting into the database. On the other hand, it can mean that more data is sent down to the browser; you should use it judiciously.

Comments, corrections, feedback welcome.

Copyright 2004 Steve Michel
7/27/2004