[ Team LiB ] Previous Section Next Section

A Date Pull-down Library

Because dates are so ubiquitous in Web interfaces and because working with dates is often comparatively nontrivial, now would seem to be a good time to look at a class library to automate some of the work that dates can present. Along the way we will revisit some of the techniques we have already covered. The simple date_pulldown library was born during the creation of a freelance job listing site. The project necessarily involved the presentation of multiple date pulldowns allowing employers to select both the start and end of contract periods, and for candidates to indicate periods of availability. A date pull-down, in this instance, is three separate select elements—one for day of the month, one for month, and another for year.

When a user submits a page, the script checks his input. If a problem exists, the page must be represented 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, but dates need to be split into month, day, and year values and then the correct option elements must be selected.

The date_pulldown class makes date pull-downs sticky (to remember settings from page to page) and easy to set.

To create our class, we first need to declare it and create a constructor. We can also declare some class properties, like so:


class date_pulldown {
  private $name;
  private $timestamp = -1;
  private $months = array("Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
  private $yearstart = -1;
  private $yearend = -1;

  function __construct( $name ) {
    $this->name = $name;
  }
// ...

We declare the $name property, which is used to name the HTML select elements. The $timestamp property holds a Unix timestamp, and the $months array property contains the strings we will display in our month pull-down. $yearstart and $yearend 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 then use to 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:


// ...
  function setDate_request( ) {
    if ( ! $this->setDate_array( $_REQUEST[$this->name] ) ) {
      return $this->setDate_timestamp( time() );
    }
    return true;
  }

  function setDate_timestamp( $time ) {
    $this->timestamp = $time;
    return true;
  }

  function setDate_array( $inputdate ) {
    if ( is_array( $inputdate ) &&
      isset( $inputdate['mon'] ) &&
      isset( $inputdate ['mday'] ) &&
      isset( $inputdate ['year'] ) ) {
      $this->timestamp = mktime( 11, 59, 59,
        $inputdate['mon'], $inputdate['mday'], $inputdate ['year'] );
      return true;
    }

    return false;
  }
// ...

Of these methods, setDate_timestamp() is the simplest. It requires a Unix timestamp and assigns it to the $timestamp property.

setDate_array() expects an associative array with at least three keys: 'mon', 'mday', and 'year'. These fields contain data in the same format as in the array returned by getdate(). This means that setDate_array() accepts a hand-built array such as


array( 'mday' => 5, 'mon' =>7, 'year' => 1999 );

or the result of a call to getDate() such as


getdate( 931172399 );

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

The setDate_request() method is called by default. It attempts to find a request argument (as held in the superglobal $_REQUEST array) with the same name as the object's $name property. This is passed to setDate_array(). If a global variable of the right structure is discovered, it is used to create the $timestamp variable. Otherwise, the current date is used.

The range for days and months is fixed, but years are a different matter. We create a few methods to allow the client coder to set her own range of years (although we also provide default behavior):


// ...
  function setYearStart( $year ) {
    $this->yearstart = $year;
  }

  function setYearEnd( $year ) {
    $this->yearend = $year;
  }

  function getYearStart() {
    if ( $this->yearstart < 0 ) {
      $nowarray = getdate( time() );
      $this->yearstart = $nowarray['year']-5;
    }
    return $this->yearstart;
  }

  function getYearEnd() {
    if ( $this->yearend < 0 ) {
      $nowarray = getdate( time() );
      $this->yearend = $nowarray['year']+5;
    }
    return $this->yearend;
  }
// ...

The setYearStart() and setYearEnd() methods are straightforward. A year is directly assigned to the appropriate property, and getYearStart() tests whether the $yearstart property has been set. If the property is not set, it assigns a $yearstart 5 years before the current year. getYearEnd() performs a similar operation. We're now ready to create the business end of the class:


// ...
  function output( ) {
    if ( $this->timestamp < 0 ) {
      $this->setDate_request();
    }
    $datearray = getdate( $this->timestamp );
    $out = $this->day_select( $this->name, $datearray );
    $out .= $this->month_select( $this->name, $datearray );
    $out .= $this->year_select( $this->name, $datearray );
    return $out;
  }

  function day_select( $fieldname, $datearray ) {
    $out = "<select name=\"$fieldname"."[mday]\">\n";
    for ( $x=1; $x<=31; $x++ ) {
      $selected = ($datearray['mday']==($x)?
        ' selected="selected"':"");
      $out .= "<option value=\"$x\"$selected>".sprintf("%02d", $x );
      $out .= "</option>\n";
    }
    $out .= "</select>\n";
    return $out;
  }

  function month_select( $fieldname, $datearray ) {
    $out = "<select name=\"$fieldname"."[mon]\">\n";
    for ( $x = 1; $x <= 12; $x++ ) {
      $selected = ($datearray['mon']==($x)?
        ' selected="selected"':"");
      $out .= "<option value=\"$x\"$selected>".$this->months[$x-1];
      $out .= "</option>\n";
    }
    $out .= "</select>\n";
    return $out;
  }

  function year_select( $fieldname, $datearray ) {
    $out = "<select name=\"$fieldname"."[year]\">";
    $start = $this->getYearStart();
    $end = $this->getYearEnd();
    for ( $x= $start; $x < $end; $x++ ) {
      $selected = ($datearray['Year']==($x)?
        ' selected="selected"':"");
      $out .= "<option value=\"$x\"$selected>$x";
      $out .= "</option>\n";
    }
    $out .= "</select>\n";
    return $out;
  }
}

The output() method orchestrates most of this code. It first checks the $timestamp property. Unless the client coder has called one of the setDate methods, it is set to -1 and setDate_global() is called by default. The timestamp is passed to the getdate() function to construct a date array, and a method is called for each pull-down to be produced.

day_select() 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. Notice that we use sprintf() to format the day number, adding a leading zero to days 1–9. month_select() and year_select() use similar logic to construct the month and year pull-downs.

In Listing 16.8, we create some code that calls the library class.

Listing 16.8 Using the date_pulldown Class
 1: <!DOCTYPE html PUBLIC
 2:   "_//W3C//DTD XHTML 1.0 Strict//EN"
 3:   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 4: <html>
 5: <head>
 6: <title>Listing 16.8 using the date_pulldown class</title>
 7: </head>
 8: <?php
 9: include("date_pulldown.class.php");
10: $date1 = new date_pulldown("fromdate");
11: $date2 = new date_pulldown("todate");
12: $date3 = new date_pulldown("foundingdate");
13: $date3->setYearStart(1971);
14: if ( empty( $_REQUEST['foundingdate'] ) ) {
15:   $date3->setDate_array( array( 'mday' =>26, 'mon'=>4, 'year'=>1984 ) );
16: }
17: ?>
18: <body>
19: <div>
20:
21: <form action="<?php echo $PHP_INFO ?>" method="post">
22: <p>
23: From:<br/>
24: <?php print $date1->output( ); ?>
25: </p>
26:
27: <p>
28: To:<br/>
29: <?php print $date2->output( ); ?>
30: </p>
31:
32: <p>
33: Company founded:<br/>
34: <?php print $date3->output( ); ?>
35: </p>
36:
37: <p>
38: <input type="submit" value="do it" />
39: </p>
40: </form>
41:
42: </div>
43: </body>
44: </html>

Notice that we've tucked the class itself away in a library file called date_pulldown. class.php, which we access using the include() statement on line 9. We use the class's default behavior for all the pull-downs except for 'foundingdate'. For this object, we override the default year start, setting it to 1972 on line 13. We also define an arbitrary date on line 14 for this pull-down that will be displayed until the form is submitted (see Figure 16.3).

Figure 16.3. The pull-downs generated by the date-pulldown class.

graphics/16fig03.gif

    [ Team LiB ] Previous Section Next Section