15.9 Creating a Calendar Date Picker

NN 6, IE 5(Win)

15.9.1 Problem

You want to provide a
pop-up calendar to assist users in
locating and entering a date into a form.

15.9.2 Solution

You can make the user interface part of a popup calendar date picker
by using a dynamic table inside an absolute-positioned
div container. Scripting behind the picker must
accomplish two primary tasks:

Populating the calendar with views of a selected month within a
selected year

Allowing the click of a date in the calendar to be delivered back to
the main document form to fill in the date field(s)

See the Discussion for an example of an HTML-based date picker, along
with style sheets and the scripts that power the calendar. One script
function, shown in Example 15-4, is called
showCalendar( ). This function is invoked by a user
interface element inside the displayed form.

To get everything initialized, an onload event
handler in the body element invokes all necessary
routines in the current page as well as the DHTML API library from
Recipe 13.3:

15.9.3 Discussion

This solution has a lot of code, including HTML, CSS, and scripts.
But there is a lot going on here: dynamically creating table content
for the calendar, setting the position and visibility of the
calendar, supplying data from the calendar back to the main page, and
more. Figure 15-3 shows the calendar being used with
a very simple date form.

Figure 15-3. Pop-up calendar picker

We'll start with two segments of HTML. One is for
the date form that ultimately receives the data from the date picker.
A click of the
button-type input element
invokes a function that displays the calendar:

The other important HTML part is the positioned
div element that holds the calendar table. It is
delivered with the page in sparse form (and hidden from view) because
scripts fill out the rest during initialization. Only the
days-of-the-week headers and select list of months are preset in the
code. Month names in the select list get used
later on to supply the name for the current month of the calendar:

The table and its components are governed by a fairly extensive style
sheet that covers positioning, visibility, table cell alignment,
background colors, fonts, and the like. Mouse rollover effects for
the date numbers in the calendar are controlled strictly by CSS
pseudoclasses of a elements:

The first script section contains several utility functions that
support others to come. First is a pair of functions,
getFirstDay( ) and getMonthLen( ),
that the calendar-creation routines use to find which day of the week
the first day of a month falls on, and then the length of the month.
The three-hour correction in getMonthLen( ) takes
care of date calculation anomalies that can occur when the month
includes a transition from summer to winter. The goal is to obtain a
valid date of the day before the first of the next month.

When it's time to display the calendar, the next
pair of functions come into play. The getElementPosition(
) function (Recipe 13.8) determines the
position of a body element (the "Pick
Date" button in our example), which the following
showCalendar( ) function uses to position the calendar
just to the right of the button before showing the calendar
(positioning and visibility are controlled from DHTML API functions).

The core routine of this application, populateTable(
), calculates the date data and
assembles the HTML for the table body portion of the calendar pop-up
window. It begins by gathering important bearings for the
calculations from select lists at the bottom of
the calendar. Then it places the month and year text in the
table's headers. After some DOM-oriented
preparations, the script removes any previous table body content. At
the heart of the script is a while loop that keeps
adding rows to the table as needed. For each row, a
for loop generates cells for each of the seven
columns, filling cells with date numbers surrounded by links that
pass the values back to the main form. A little extra touch is
labeling the ID of the current day's cell so that it
picks up one of the style sheet rules to make it stand out from the
rest of the cells.

Two initialization routines, fillYears(
) and setCurrMonth(
), prepare the select elements in the
calendar so that the years in the list constantly move forward as
time marches on. Also, the lists are set to the current month and
year as a starting point for the user the first time the calendar
appears.

When the user clicks on one of the dates in the calendar, the links
for each date invoke the chooseDate(
) function, passing parameters for the
date, month, and year. The parameters are assigned to the event
handlers of the calendar date links while the calendar
month's HTML is assembled back in
populateTable( ). The chooseDate(
) function in this example distributes the values to the
three date fields in the original form.

This pop-up calendar works in Internet Explorer 5 or later for
Windows and Netscape 6 or later. Unfortunately, table modification
bugs in IE 5 for the Mac prevent it from working in that environment.

Netscape (at least through Version 7) exhibits a lingering oddity
when the calendar div overlaps form controls,
especially text-oriented fields. A rendering conflict causes the text
field to supersede the positioned div such that
you cannot click on calendar date links or activate the
select lists if any of them are on top of a field.
Therefore, you should endeavor to design your page and the position
of the calendar div such that the
div does not come into contact with form controls.
IE does not have this problem.

Most of the visible, fun part of this application is governed by
style sheets for the calendar table. You have wide flexibility in
designing your calendar by using the HTML tags and IDs of the
skeletal calendar table as a guide. If you adhere to those naming
conventions, the calendar-generating and modifying code will work
without any problems.

Another potential modification that might appeal to you is to make
the calendar draggable by its titlebar. You can adapt the
element-dragging code from Recipe 13.11 to add that functionality
here, as well.