Sams Teach Yourself PHP, MySQL and Apache All in One (2012)

Part V. Basic Projects

Chapter 24. Creating a Simple Calendar


In this chapter, you learn the following:

• How to build a simple calendar script

• How to view and add events in your calendar

• How to build a class library to generate date pull-downs in HTML forms


This chapter pulls together the skills you’ve learned so far regarding the PHP language and building small CRUD applications—those that create, retrieve, update, and delete data. In this chapter, you continue your learning in the context of creating a small calendar application.

Building a Simple Display Calendar

You’ll use the date and time functions you learned in Chapter 10, “Working with Strings, Dates, and Time,” to build a calendar that displays the dates for any month between 1990 and 2020. Those are randomly selected years and have no significance—you can make your calendar go from 1980 to 2025 if you want, or any other range of dates that make sense to you. The user can select both month and year with pull-down menus, and the dates for the selected month will be organized according to the days of the week.

In this script, we work with two variables—one for month and one for year—which are supplied by user input. These pieces of information are used to build a timestamp based on the first day of the selected month. If user input is invalid or absent, the default value is the first day of the current month.

Checking User Input

When the user accesses the calendar application for the first time, no information will have been submitted. Therefore, we must ensure that the script can handle the fact that the variables for month and year might not be defined. We could use the isset() function for this because it returnsfalse if the variable passed to it has not been defined. However, let’s use the checkdate() function instead, which not only will see whether the variable exists but will also do something meaningful with it, namely, validate that it is a date. Listing 24.1 shows the fragment of code that checks for month and year variables coming from a form, and builds a timestamp based on them.

Listing 24.1 Checking User Input for the Calendar Script


 1: <?php
 2: if ((!isset($_POST['month'])) || (!isset($_POST['year']))) {
 3:     $nowArray = getdate();
 4:     $month = $nowArray['mon'];
 5:     $year = $nowArray['year'];
 6: } else {
 7:     $month = $_POST['month'];
 8:     $year = $_POST['year'];
 9: }
10: $start = mktime (12, 0, 0, $month, 1, $year);
11: $firstDayArray = getdate($start);
12: ?>


Listing 24.1 is a fragment of a larger script, so it does not produce any output itself. But it’s an important fragment to understand, which is why it sits all alone here, ready for an explanation.

In the if statement on line 2, we test whether the month and year have been provided by a form. If the month and year have not been defined, the mktime() function used later in the fragment will not be able to make a valid date from undefined month and year arguments.

If the values are present, we use getdate() on line 3 to create an associative array based on the current time. We then set values for $month and $year ourselves, using the array’s mon and year elements (lines 4 and 5). If the variables have been set from the form, we put the data into $month and$year variables so as not to touch the values in the original $_POST superglobal.

Now that we are sure that we have valid data in $month and $year, we can use mktime() to create a timestamp for the first day of the month (line 10). We will need information about this timestamp later on, so on line 11, we create a variable called $firstDayArray that will store an associative array returned by getdate() and based on this timestamp.

Building the HTML Form

We now need to create an interface by which users can ask to see data for a month and year. For this, we use SELECT elements. Although we could hard-code these in HTML, we must also ensure that the pull-downs default to the currently chosen month, so we will dynamically create these pull-downs, adding a SELECT attribute to the OPTION element where appropriate. The form is generated in Listing 24.2.

Listing 24.2 Building the HTML Form for the Calendar Script


1: <?php
2: if ((!isset($_POST['month'])) || (!isset($_POST['year']))) {
3:     $nowArray = getdate();
4:     $month = $nowArray['mon'];
5:     $year = $nowArray['year'];
6:  } else {
7:     $month = $_POST['month'];
8:     $year = $_POST['year'];
9:  }
10: $start = mktime (12, 0, 0, $month, 1, $year);
11: $firstDayArray = getdate($start);
12: ?>
13: <!DOCTYPE html>
14: <html>
15: <head>
16: <title><?php echo "Calendar:".$firstDayArray['month']."
17:  ".$firstDayArray['year']; ?></title>
18: <body>
19: <h1>Select a Month/Year Combination</h1>
20: <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
21: <select name="month">
22: <?php
23: $months = Array("January", "February", "March", "April", "May",
24: "June", "July", "August", "September", "October", "November", "December");
25: for ($x=1; $x <= count($months); $x++) {
26:     echo"<option value=\"$x\"";
27:     if ($x == $month) {
28:         echo " selected";
29:     }
30:     echo ">".$months[$x-1]."</option>";
31: }
32: ?>
33: </select>
34: <select name="year">
35: <?php
36: for ($x=1990; $x<=2020; $x++) {
37:     echo "<option";
38:     if ($x == $year) {
39:         echo " selected";
40:     }
41:     echo ">$x</option>";
42: }
43: ?>
44: </select>
45: <button type="submit" name="submit" value="submit">Go!</button>
46: </form>
47: </body>
48: </html>


Having created the $start timestamp and the $firstDayArray date array in lines 2–11, let’s begin to write the HTML for the page. Notice that we use $firstDayArray to add the month and year to the TITLE element on lines 16 and 17.

Line 20 is the beginning of our form. To create the SELECT element for the month pull-down, we drop back into PHP mode on line 22 to write the individual OPTION tags. First, for display purposes, we create in lines 23 and 24 an array called $months that contains the names of the 12 months. We then loop through this array, creating an OPTION tag for each name (lines 25–31).

This is an overcomplicated way of writing a simple SELECT element were it not for the fact that we are testing $x (the counter variable in the for statement) against the $month variable on line 27. If $x and $month are equivalent, we add the string SELECTED to the OPTION tag, ensuring that the correct month will be selected automatically when the page loads. We use a similar technique to write the year pull-down on lines 36–42. Finally, back in HTML mode, we create a submit button on line 45.

We now have a form that can send the month and year parameters to itself and will default either to the current month and year or the month and year previously chosen. If you save this listing as dateselector.php, place it in your web server document root, and access it with your web browser, you should see something like Figure 24.1. (Your month and year might differ.)

image

Figure 24.1 The calendar form.

Creating the Calendar Table

We now need to create a table and populate it with dates for the chosen month. We do this in Listing 24.3, which represents the complete calendar display script.

Although line 2 is new, lines 3–64 should be familiar from your work with Listing 24.2, with some stylesheet entries added in lines 19–35. That addition in line 2 simply defines a constant variable, in this case ADAY (for example, “a day”) with a value of 86400. This value represents the number of seconds in a day, which the script uses later.

Listing 24.3 The Complete Calendar Display Script


1: <?php
2: define("ADAY", (60*60*24));
3: if ((!isset($_POST['month'])) || (!isset($_POST['year']))) {
4:     $nowArray = getdate();
5:     $month = $nowArray['mon'];
6:     $year = $nowArray['year'];
7:  } else {
8:     $month = $_POST['month'];
9:     $year = $_POST['year'];
10:  }
11: $start = mktime (12, 0, 0, $month, 1, $year);
12: $firstDayArray = getdate($start);
13: ?>
14: <!DOCTYPE html>
15: <html>
16: <head>
17: <title><?php echo "Calendar: ".$firstDayArray['month']."
18:  ".$firstDayArray['year'']; ?></title>
19: <style type="text/css">
20:    table {
21:        border: 1px solid black;
22:        border-collapse: collapse;
23:    }
24:    th {
25:        border: 1px solid black;
26:        padding: 6px;
27:        font-weight: bold;
28:        background: #ccc;
29:    }
30:    td {
31:        border: 1px solid black;
32:        padding: 6px;
33:        vertical-align: top;
34:        width: 100px;
35:    }
36: </style>
37: <body>
38: <h1>Select a Month/Year Combination</h1>
39: <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
40: <select name="month">
41: <?php
42: $months = Array("January", "February", "March", "April", "May",
43: "June", "July", "August", "September", "October", "November", "December");
44: for ($x=1; $x <= count($months); $x++) {
45:     echo"<option value=\"$x\"";
46:     if ($x == $month) {
47:         echo " selected";
48:     }
49:     echo ">".$months[$x-1]."</option>";
50: }
51: ?>
52: </select>
53: <select name="year">
54: <?php
55: for ($x=1980; $x<=2010; $x++) {
56:     echo "<option";
57:     if ($x == $year) {
58:         echo " selected";
59:     }
60:     echo ">$x</option>";
61: }
62: ?>
63: </select>
64: <button type="submit" name="submit" value="submit">Go!</button>
65: </form>
66: <br/>
67: <?php
68: $days = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
69: echo "<table><tr>\n";
70: foreach ($days as $day) {
71:     echo "<td>".$day.</td>\n";
72: }
73: for ($count=0; $count < (6*7); $count++) {
74:     $dayArray = getdate($start);
75:     if (($count % 7) == 0) {
76:          if ($dayArray['mon'] != $month) {
77:             break;
78:          } else {
79:             echo "</tr><tr>\n";
80:         }
81:    }
82:    if ($count < $firstDayArray['wday'] || $dayArray['mon'] != $month) {
83:        echo "<td> </td>\n";
84:    } else {
85:        echo "<td>".$dayArray['mday']."</td>\n";
86:        $start += ADAY;
87:    }
88: }
89: echo "</tr></table>";
90: ?>
91: </body>
92: </html>


We pick up the entirely new code at line 66 of Listing 24.3. Because the table will be indexed by days of the week, we loop through an array of day names in lines 70–72, printing each in its own table cell, on line 71. All the real magic of the script happens in the final for statement beginning on line 73.

In line 73, we initialize a variable called $count and ensure that the loop will end after 42 iterations. This is to make sure that we will have enough cells to populate with date information, taking into consideration that a four-week month might actually have partial weeks at the beginning and the end, thus the need for six 7-day weeks (rows).

Within this for loop, we transform the $start variable into a date array with getdate(), assigning the result to $dayArray (line 73). Although $start is the first day of the month during the loop’s initial execution, we will increment this timestamp by the value of ADAY (24 hours) for every iteration (see line 85).

On line 75, we test the $count variable against the number 7, using the modulus operator. The block of code belonging to this if statement will therefore be run only when $count is either zero or a multiple of 7. This is our way of knowing whether we should end the loop altogether or start a new row, where rows represent weeks.

After we have established that we are in the first iteration or at the end of a row, we can go on to perform another test on line 76. If the mon (month number) element of the $dayArray is no longer equivalent to the $month variable, we are finished. Remember that $dayArray contains information about the $start timestamp, which is the current place in the month that we are displaying. When $start goes beyond the current month, $dayArray["mon"] will hold a different figure than the $month number provided by user input. Our modulus test demonstrated that we are at the end of a row, and the fact that we are in a new month means that we can leave the loop altogether. Assuming, however, that we are still in the month that we are displaying, we end the row and start a new one on line 79.

In the next if statement, on line 82, we determine whether to write date information to a cell. Not every month begins on a Sunday, so it’s likely that our rows will contain an empty cell or two. Similarly, few months will finish at the end of one of our rows, so it’s also likely that we will have a few empty cells before we close the table.

We have stored information about the first day of the $firstDayArray; in particular, we can access the number of the day of the week in $firstDayArray['wday']. If the value of $count is smaller than this number, we know that we haven’t yet reached the correct cell for writing. By the same token, if the value of the $month variable is no longer equal to $dayArray['mon'], we know that we have reached the end of the month (but not the end of the row, as we determined in our earlier modulus test). In either case, we write an empty cell to the browser on line 83.

In the final else clause on line 84, we can do the fun stuff. We have already determined that we are within the month that we want to list, and that the current day column matches the day number stored in $firstDayArray['wday']. Now we must use the $dayArray associative array that we established early in the loop to write the day of the month and some blank space into a cell.

Finally, on line 86, we need to increment the $start variable, which contains our date stamp. We just add the number of seconds in a day to it (we defined this value in line 2), and we’re ready to begin the loop again with a new value in $start to be tested. If you save this listing asshowcalendar.php, place it in your web server document root, and access it with your web browser, you should see something like Figure 24.2 (your month and year might differ).

image

Figure 24.2 The calendar form and script.

Adding Events to the Calendar

Displaying the calendar is great, but with just a few extra lines of code, you can make it interactive—that is, you can add and view events on a given day. To begin, let’s create a simple database table that holds event information. For purposes of simplicity, these events will occur on only a single day and only their start date and time will be shown. Although you can make the event entries as complex as you want, this example is here just to show the basic process involved.

The calendar_events table will include fields for the start date and time, the event title, and an event short description:

CREATE TABLE calendar_events (
     id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
     event_title VARCHAR (25),
     event_shortdesc VARCHAR (255),
     event_start DATETIME
);

We can use the code in Listing 24.3 as our base (the script called showcalendar.php). In this new script, we add a link to a pop-up window as part of the calendar display. Each date is a link; the pop-up window calls another script that displays the full text of an event as well as provide the capability to add an event. To begin, add the following JavaScript code in the <head> area, before the opening <body> tag—after line 36 of the original script in Listing 24.3:

<script type="text/javascript">
function eventWindow(url) {
    event_popupWin = window.open(url, 'event', 'resizable=yes, scrollbars=yes,
            toolbar=no,width=400,height=400');
    event_popupWin.opener = self;
}
</script>

This JavaScript function defines a 400×400 window that will call a URL we provide. We use this JavaScript function to replace what was line 85 of the original script in Listing 24.3; we now wrap the date display in this link to the JavaScript-based pop-up window, which calls a script namedevent.php. The new code is as follows:

echo "<td><a href=\"javascript:eventWindow('event.php?m=".$month.
"&d=".$dayArray['mday']."&y=$year');\">".$dayArray['mday']."</a>
<br/><br/>".$event_title."</td>\n";

Not only do we call the event.php file, but we also have to send along with it the date information for the particular link that is clicked. This is done via the query string, and you can see we’re sending along three variables—what will become $_GET['m'] for the month, $_GET['d'] for the day, and $_GET['y'] for the year.

Only one change remains for this particular script before we tackle the event.php script—adding an indicator to this particular view, if events do indeed exist. The query that checks for existing events on a given day appears at the onset of the else statement that was originally found at line 84. An entirely new else statement is shown; you can see that the database connection is made, a query is issued, and, if results are found, text is printed within the table cell for that day:

} else {
     $mysqli = mysqli_connect("localhost", "joeuser", "somepass", "testDB");
     $chkEvent_sql = "SELECT event_title FROM calendar_events WHERE
                      month(event_start) = '".$month."' AND
                      dayofmonth(event_start) = '".$dayArray['mday']."'
                      AND year(event_start) = '".$year."' ORDER BY event_start";
     $chkEvent_res = mysqli_query($mysqli, $chkEvent_sql)
                     or die(mysqli_error($mysqli));

     if (mysqli_num_rows($chkEvent_res) > 0) {
          while ($ev = mysqli_fetch_array($chkEvent_res)) {
               $event_title = stripslashes($ev['event_title']);
          }
     } else {
          $event_title = "";
     }
     echo "<td><a href=\"javascript:eventWindow('event.php?m=".$month.
     "&d=".$dayArray['mday']."&y=$year');\">".
     $dayArray['mday']."</a><br/><br/>".$event_title."</td>\n";

     unset($event_title);

     $start += ADAY;
}

In Listing 24.4, you can see the entirely new script, which we’ll call showcalendar_withevent.php.

Listing 24.4 Calendar Display Script with Entry-Related Modifications


1:  <?php
2:  define("ADAY", (60*60*24));
3:  if ((!isset($_POST['month'])) || (!isset($_POST['year']))) {
4:      $nowArray = getdate();
5:      $month = $nowArray['mon'];
6:      $year = $nowArray['year'];
7:  } else {
8:       $month = $_POST['month'];
9:       $year = $_POST['year'];
10:  }
11:  $start = mktime (12, 0, 0, $month, 1, $year);
12:  $firstDayArray = getdate($start);
13:  ?>
14: <!DOCTYPE html>
15:  <html>
16:  <head>
17:  <title><?php echo "Calendar: ".$firstDayArray['month']." ".
18:  $firstDayArray['year'] ?></title>
19:  <head>
20:  <style type="text/css">
21:      table {
22:         border: 1px solid black;
23:         border-collapse: collapse;
24:      }
25:      th {
26:         border: 1px solid black;
27:         padding: 6px;
28:         font-weight: bold;
29:         background: #ccc;
30:      }
31:      td {
32:         border: 1px solid black;
33:         padding: 6px;
34:         vertical-align: top;
35:         width: 100px;
36:      }
37:  </style>
38:  <script type="text/javascript">
39:  function eventWindow(url) {
40:      event_popupWin = window.open(url, 'event',
41:      'resizable=yes,scrollbars=yes,toolbar=no,width=400,height=400');
42:      event_popupWin.opener = self;
43:  }
44:  </script>
45:  <body>
46:  <h1>Select a Month/Year Combination</h1>
47:  <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
48:  <select name="month">
49:  <?php
50:  $months = Array("January", "February", "March", "April", "May",  "June",
51:  "July", "August", "September", "October", "November", "December");
52:  for ($x=1; $x <= count($months); $x++) {
53:      echo"<option value=\"$x\"";
54:      if ($x == $month) {
55:          echo " selected";
56:      }
57:      echo ">".$months[$x-1]."</option>";
58:  }
59:  ?>
60:  </select>
61:  <select name="year">
62:  <?php
63:  for ($x=1990; $x<=2020; $x++) {
64:       echo "<option";
65:       if ($x == $year) {
66:           echo " selected";
67:       }
68:       echo ">$x</option>";
69:  }
70:  ?>
71:  </select>
72:  <button type="submit" name="submit" value="submit">Go!</button>
73:  </form>
74:  <br/>
75:  <?php
76:  $days = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
77:  echo "<table><tr>\n";
78:  foreach ($days as $day) {
79:      echo "<th>$day</th>\n";
80:  }
81:
82:  for ($count=0; $count < (6*7); $count++) {
83:      $dayArray = getdate($start);
84:      if (($count % 7) == 0) {
85:          if ($dayArray['mon'] != $month) {
86:              break;
87:          } else {
88:              echo "</tr><tr>\n";
89:          }
90:      }
91:      if ($count < $firstDayArray['wday'] || $dayArray['mon'] != $month) {
92:          echo "<td> </td>\n";
93:      } else {
94:           $mysqli = mysqli_connect("localhost", "joeuser", "somepass",   "testDB");
95:           $chkEvent_sql = "SELECT event_title FROM calendar_events WHERE
96:                            month(event_start) = '".$month."' AND
97:                            dayofmonth(event_start) = '".$dayArray['mday']."'
98:                            AND year(event_start) = '".$year."' ORDER BY   event_start";
99:           $chkEvent_res = mysqli_query($mysqli, $chkEvent_sql)
100:                          or die(mysqli_error($mysqli));
101:
102:          if (mysqli_num_rows($chkEvent_res) > 0) {
103:                while ($ev = mysqli_fetch_array($chkEvent_res)) {
104:                  $event_title .= stripslashes($ev['event_title']."<br/>";
105:                }
106:          } else {
107:               $event_title = "";
108:          }
109:
110:          echo "<td><a href=\"javascript:eventWindow('event.php?m=".$month.
111:          "&d=".$dayArray['mday']."&y=$year');\">".
112:          $dayArray['mday']."</a><br/><br/>".$event_title."</td>\n";
113:
114:          unset($event_title);
115:          $start += ADAY;
116:    }
117: }
118: echo "</tr></table>";
119:
120: //close connection to MySQL
121: mysqli_close($mysqli);
122: ?>
123: </body>
124: </html>


In Figure 24.3, you can see the new calendar, including the representation of the event title on a date that, for illustrative purposes here, I’ve prepopulated with an event in the calendar_events table.

image

Figure 24.3 Showing the calendar with an event.

All that remains is adding the all-in-one event.php script used in the pop-up window to display and also add an event to the calendar (on a particular day). Listing 24.5 contains all the necessary code; the fun part starts at line 8, which connects to the MySQL database. Line 11 checks whether the event entry form has been submitted; if it has, database-safe values are created in lines 14–24, and an INSERT statement is created and issued to add the event to the calendar_events table before continuing (lines 29–34).

Listing 24.5 Showing Events/Adding Events via Pop-Up


1:   <!DOCTYPE html>
2:   <html>
3:   <head>
4:   <title>Show/Add Events</title>
5:   <body>
6:   <h1>Show/Add Events</h1>
7:   <?php
8:   $mysqli = mysqli_connect("localhost", "joeuser", "somepass", "testDB");
9:
10:  //add any new event
11:  if ($_POST) {
12:
13:     //create database-safe strings
14:     $safe_m = mysqli_real_escape_string($mysqli, $_POST['m']);
15:     $safe_d = mysqli_real_escape_string($mysqli, $_POST['d']);
16:     $safe_y = mysqli_real_escape_string($mysqli, $_POST['y']);
17:     $safe_event_title = mysqli_real_escape_string($mysqli,
18:        $_POST['event_title']);
19:     $safe_event_shortdesc = mysqli_real_escape_string($mysqli,
20:        $_POST['event_shortdesc']);
21:     $safe_event_time_hh = mysqli_real_escape_string($mysqli,
22:       $_POST['event_time_hh']);
23:     $safe_event_time_mm = mysqli_real_escape_string($mysqli,
24:        $_POST['event_time_mm']);
25:
26:     $event_date = $safe_y."-".$safe_m."-".$safe_d."
27:        ".$safe_event_time_hh.":".$safe_event_time_mm.":00";
28:
29:     $insEvent_sql = "INSERT INTO calendar_events (event_title,
30:                 event_shortdesc, event_start) VALUES
31:                ('".$safe_event_title."', '".$safe_event_shortdesc."',
32:                '".$event_date."')";
33:      $insEvent_res = mysqli_query($mysqli, $insEvent_sql)
34:           or die(mysqli_error($mysqli));
35:
36:  } else {
37:
38:      //create database-safe strings
39:      $safe_m = mysqli_real_escape_string($mysqli, $_GET['m']);
40:      $safe_d = mysqli_real_escape_string($mysqli, $_GET['d']);
41:      $safe_y = mysqli_real_escape_string($mysqli, $_GET['y']);
42:  }
43:
44:  //show events for this day
45:  $getEvent_sql = "SELECT event_title, event_shortdesc,
46:                  date_format(event_start, '%l:%i %p') as fmt_date
47:                  FROM calendar_events WHERE month(event_start) =
48:                  '".$safe_m."' AND dayofmonth(event_start) =
49:                  '".$safe_d."' AND year(event_start) =
50:                  '".$safe_y."' ORDER BY event_start";
51:  $getEvent_res = mysqli_query($mysqli, $getEvent_sql)
52:       or die(mysqli_error($mysqli));
53:
54:  if (mysqli_num_rows($getEvent_res) > 0) {
55:      $event_txt = "<ul>";
56:      while ($ev = @mysqli_fetch_array($getEvent_res)) {
57:         $event_title = stripslashes($ev['event_title']);
58:         $event_shortdesc = stripslashes($ev['event_shortdesc']);
59:         $fmt_date = $ev['fmt_date'];
60:         $event_txt .= "<li><strong>".$fmt_date."</strong>:
61:                ".$event_title."<br/>".$event_shortdesc."</li>";
62:       }
63:       $event_txt .= "</ul>";
64:       mysqli_free_result($getEvent_res);
65:  } else {
66:       $event_txt = "";
67:  }
68:  // close connection to MySQL
69:  mysqli_close($mysqli);
70:
71:  if ($event_txt != "") {
72:      echo "<p><strong>Today's Events:</strong></p>
73:      $event_txt
74:      <hr/>";
75:  }
76:
77:  // show form for adding an event
78:  echo <<<END_OF_TEXT
79:  <form method="post" action="$_SERVER[PHP_SELF]">
80:  <p><strong>Would you like to add an event?</strong><br/>
81:  Complete the form below and press the submit button to
82:  add the event and refresh this window.</p>
83:
84:  <p><label for="event_title">Event Title:</label><br/>
85:  <input type="text" id="event_title" name="event_title"
86:         size="25" maxlength="25" /></p>
87:
88:  <p><label for="event_shortdesc">Event Description:</label><br/>
89:  <input type="text" id="event_shortdesc" name="event_shortdesc"
90:         size="25" maxlength="255" /></p>
91:  <fieldset>
92:  <legend>Event Time (hh:mm):</legend>
93:  <select name="event_time_hh">
94:  END_OF_TEXT;
95:
96:  for ($x=1; $x <= 24; $x++) {
97:   echo "<option value=\"$x\">$x</option>";
98:  }
99:
100: echo <<<END_OF_TEXT
101: </select> :
102: <select name="event_time_mm">
103: <option value="00">00</option>
104: <option value="15">15</option>
105: <option value="30">30</option>
106: <option value="45">45</option>
107: </select>
108: </fieldset>
109: <input type="hidden" name="m" value="$safe_m">
110: <input type="hidden" name="d" value="$safe_d">
111: <input type="hidden" name="y" value="$safe_y">
112:
113: <button type="submit" name="submit" value="submit">Add Event</button>
114: </form>
115: END_OF_TEXT;
116: ?>
117: </body>
118: </html>


Lines 45–52 create and issue the query and retrieve all records that correspond to events on this given day. The text block used to display entries is created in lines 54–67. However, users also need to see the form for adding an event, and this is built in lines 79–114, effectively the end of the script.

Figure 24.4 shows how a pop-up looks when a link is followed from the calendar and an entry is already present. In this example, we wanted to add another event on this day, so the form has been completed in preparation for adding the additional event.

image

Figure 24.4 Showing the day detail, ready to add another event.

In Figure 24.5, a second event has been added to this particular day.

image

Figure 24.5 A second event has been added.

Obviously, this is a simple example, but it shows that it is indeed easy to build a calendar-based application in just a few short scripts.

Creating a Calendar Library

Because dates are ubiquitous in web-based application interfaces, and because working with dates can be complicated, let’s look at creating a class library to automate some of the work you might do with dates. Along the way, we revisit some of the techniques we have already covered in this book—especially in Chapter 9, “Working with Objects.”

The simple date_pulldown library created in this instance will consist of three separate select elements: one for day of the month, one for month, and one for year. When a user submits a page, the script will verify the form input. If there is a problem with the input, we will refresh the page with the user’s input still in place. This is easy to accomplish with text boxes but is more of a chore with pull-down menus.

Pages that display information pulled from a database present a similar problem. Data can be entered straight into the value attributes of text-type input elements. Dates will need to be split into month, day, and year values, and then the correct option elements selected.

The date_pulldown class aims to make date pull-downs sticky (so that they will remember settings from page to page) and easy to set. To create our class, we first need to declare it and create a constructor.


Note

constructor is a function that exists within a class and is automatically called when a new instance of the class is created.


We can also declare some class properties. We will step through Listing 24.6; this first snippet shows the beginning of the class.

Listing 24.6 The Calendar Library Class


 1: class date_pulldown {
 2:     public $name;
 3:     public $timestamp = -1;
 4:     public $months = array("Jan", "Feb", "Mar", "Apr", "May", "Jun",
 5:           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
 6:     public $yearstart = -1;
 7:     public $yearend = -1;
 8:
 9:     function date_pulldown($name) {
10:         $this->name = $name;
11:   }


We first declare the $name property on line 2. We use it to name the HTML select elements. The $timestamp property, defined on line 3, will hold a UNIX timestamp. The $months array property, defined on lines 4–5, contains the strings we display in our month pull-down. The $yearstart and$yearend properties (lines 6 and 7) are both set to –1, pending initialization. They will eventually hold the first and last years of the range that will be presented in the year pull-down.

The constructor is simple. It accepts a string, which we assign to the $name property. Now that we have the basis of our class, we need a set of methods by which the client code can set the date. Listing 24.6 continues as follows.

Listing 24.6 (continued)


12:    function setDate_global( ) {
13:        if (!$this->setDate_array($GLOBALS[$this->name])) {
14:            return $this->setDate_timestamp(time());
15:        }
16:        return true;
17:    }
18:
19:    function setDate_timestamp($time) {
20:        $this->timestamp = $time;
21:        return true;
22:    }
23:
24:    function setDate_array($inputdate) {
25:        if (is_array($inputdate) &&
26:            isset($inputdate['mon']) &&
27:            isset($inputdate['mday']) &&
28:            isset($inputdate['year'])) {
29:
30:            $this->timestamp = mktime(11, 59, 59,
31:                $inputdate['mon'], $inputdate['mday'], $inputdate['year']);
32:            return true;
33:        }
34:        return false;
35:    }


Of the methods shown here, setDate_timestamp() is the simplest (lines 19–22). It requires a UNIX timestamp, which it assigns to the $timestamp property. But let’s not forget the others.

The setDate_array() method (lines 24–35) expects an associative array with at least three keys: mon, mday, and year. These keys will have data values in the same format as in the array returned by getdate(). This means that setDate_array() will accept a hand-built array such as

array("mday"=> 25, "mon"=>3, "year"=> 2012);

or the result of a call to getdate():

getdate(1208052013);

It is no accident that the pull-downs we build later will be constructed to produce an array containing mon, mday, and year keys. The method uses the mktime() function to construct a timestamp, which is then assigned to the $timestamp variable.

The setDate_global() method (lines 12–17) is called by default. It attempts to find a global variable with the same name as the object’s $name property. This is passed to setDate_array(). If this method discovers a global variable of the right structure, it uses that variable to create the$timestamp variable. Otherwise, the current date is used.

The ranges for days and months are fixed, but years are a different matter. As Listing 24.6 continues, we create a few methods to allow the client code to set a range of years, although we also provide default behavior.

Listing 24.6 (continued)


36:    function setYearStart($year) {
37:        $this->yearstart = $year;
38:    }
38:
40:    function setYearEnd($year) {
41:        $this->yearend = $year;
42:    }
43:
44:    function getYearStart() {
45:        if ($this->yearstart < 0) {
46:            $nowarray = getdate(time());
47:            $this->yearstart = $nowarray['year']-5;
48:        }
49:
50:        return $this->yearstart;
51:    }
52:
53:    function getYearEnd() {
54:        if ($this->yearend < 0) {
55:            $nowarray = getdate(time());
56:            $this->yearend = $nowarray['year']+5;
57:        }
58:        return $this->yearend;
59:    }


The setYearStart() and setYearEnd() methods (lines 36–43) are straightforward in that a year is directly assigned to the appropriate property. The getYearStart() method tests whether the $yearstart property has been set, and if it has not, the method assigns a $yearstart value 5 years before the current year. The getYearEnd() method performs a similar operation.

We’re now ready to create the business end of the class as Listing 24.6 continues.

Listing 24.6 (continued)


 60:    function output() {
 61:        if ($this->timestamp < 0) {
 62:            $this->setDate_global();
 63:        }
 64:        $datearray = getdate($this->timestamp);
 65:        $out  = $this->day_select($this->name, $datearray);
 66:        $out .= $this->month_select($this->name, $datearray);
 67:        $out .= $this->year_select($this->name, $datearray);
 68:        return $out;
 69:    }
 70:
 71:    function day_select($fieldname, $datearray)  {
 72:        $out = "<select name=\"$fieldname"."['mday']\">\n";
 73:        for ($x=1; $x<=31; $x++) {
 74:            $out .= "<option value=\"$x\"".($datearray['mday']==($x)
 75:            ?" selected":"").">".sprintf("%02d", $x) ."</option>\n";
 76:        }
 77:        $out .= "</select>\n";
 78:        return $out;
 79:    }
 80:
 81:    function month_select($fieldname, $datearray) {
 82:        $out = "<select name=\"$fieldname"."['mon']\">\n";
 83:        for ($x = 1; $x <= 12; $x++) {
 84:            $out .= "<option value=\"".($x)."\"".($datearray['mon']==($x)
 85:            ?" selected":"")."> ".$this->months[$x-1]."</option>\n";
 86:        }
 87:        $out .= "</select>\n";
 88:        return $out;
 89:    }
 90:
 91:    function year_select($fieldname, $datearray) {
 92:        $out = "<select name=\"$fieldname"."['year']\">";
 93:        $start = $this->getYearStart();
 94:        $end = $this->getYearEnd();
 95:        for ($x= $start; $x < $end; $x++) {
 96:            $out .= "<option value=\"$x\"".($datearray['year']==($x)
 97:            ?" selected":"").">".$x."</option>\n";
 98:        }
 99:        $out .= "</select>\n";
100:        return $out;
101:    }
102: }


The output() method orchestrates most of this code (lines 60–69). It first checks the $timestamp property, and, unless one of the setDate methods has been called, the value of $timestamp will be set to –1 and setDate_global() will be called by default. The timestamp is passed to thegetdate() function to construct a date array, and a method is called to produce each pull-down.

The day_select() method (lines 71–79) simply constructs an HTML select element with an option element for each of the 31 possible days in a month. The object’s current date is stored in the $datearray argument variable, which is used during the construction of the element to set the selected attribute of the relevant option element. The sprintf() function formats the day number, adding a leading zero to days 1–9. The month_select() and year_select() methods (lines 81–101) use similar logic to construct the month and year pull-downs.

Why did we break down the output code into four methods, rather than simply creating one block of code? When we build a class, we have two kinds of programmer in mind: one who will want to instantiate a date_pulldown object, and one who will want to subclass the date_pulldown class to refine its functionality.

For the former, we want to provide a simple and clear interface to the class’s functionality. The programmer can then instantiate an object, set its date, and call the output() method. For the latter, we want to make it easy to change discrete elements of the class’s functionality. By putting all the output code into one method, we would force a child class that needed to tweak output to reproduce a lot of code that is perfectly usable. By breaking this code into discrete methods, we allow for subclasses that can change limited aspects of functionality without disturbing the whole. If a child class needs to represent the year pull-down as two radio buttons, for example, you can simply override the year_select() method.

Listing 24.7 contains some code that calls the library class. Before you try to execute this code, take the code from Listing 24.6, surround it with PHP opening and closing tags, and save it into a file called date_pulldown.class.php. Place this file in the document root of your web server because Listing 24.7 uses it and it had better be there!

Listing 24.7 Using the date_pulldown Class


1:  <!DOCTYPE html>
2:  <html>
3:  <head>
4:  <title>Using the date_pulldown Class</title>
5:  </head>
6:  <?php
7:  include("date_pulldown.class.php");
8:  $date1 = new date_pulldown("fromdate");
9:  $date2 = new date_pulldown("todate");
10: $date3 = new date_pulldown("foundingdate");
11: $date3->setYearStart("1972");
12: if (empty($foundingdate)) {
13:    $date3->setDate_array(array('mday'=>26, 'mon'=>4, 'year'=>1984));
14: }
15: ?>
16: <body>
17: <form>
18: <p><strong>From:</strong><br/>
19: <?php echo $date1->output(); ?></p>
20:
21: <p><strong>To:</strong><br/>
22: <?php echo $date2->output(); ?></p>
23:
24: <p><strong>Company Founded:</strong><br/>
25: <?php echo $date3->output(); ?></p>
26:
27: <button type="submit" name="submit" value="submit">Submit</button>
28: </form>
29: </body>
30: </html>


On line 7, we include the date_pulldown.class.php; after we have included the class file, we can use all of its methods. We use the class’s default behavior for all the pull-downs, apart from "foundingdate". For this particular object, we override the default year start, setting it to 1972 on line 11. On line 13, we assign this pull-down an arbitrary date that will be displayed until the form is submitted (see Figure 24.6).

image

Figure 24.6 The pull-downs generated by the date_pulldown class.


Note

This is only the front end of a form, with no action or method; you need to supply your own action or method for this to actually do something.


Summary

In this chapter, you pulled together the PHP date-related functions you learned about earlier in the book to work within a basic calendar application display. You learned how to test the validity of an input date using checkdate(), and you worked through a sample script that applied some of the tools you have learned. You also saw one method for adding and viewing events within your calendar application. You also learned how to build a date-related class library that can be used to automate some of the more tedious aspects of working with dates within HTML forms.

Q&A

Q. Are there any functions for converting between different calendars?

A. Yes. PHP provides an entire suite of functions that cover alternative calendars. You can read about these in the official PHP Manual at http://www.php.net/manual/en/ref.calendar.php.

Workshop

The workshop is designed to help you review what you’ve learned and begin putting your knowledge into practice.

Quiz

1. What PHP function did we use to create a timestamp?

2. What PHP function did we use to create an associative array of date-related information?

3. Why are the variables in the date_pulldown class declared as public?

Answers

1. mktime()

2. getdate()

3. Public variables are available outside of the class, as these variables need to be.

Activities

1. Modify the calendar display script to show an entire year of the calendar—from January through December. After that, display the calendar as a 3×4 grid, or four rows of three months on each row.

2. Modify the event-creation script to use the date pull-down class.