Chapter 22

Creating Groupware Calendars


CONTENTS

One of the latest interests on the business-oriented side of the Internet community is the utilization of Internet technology for the creation of groupware applications-programs that allow people, across a network, to share information.

Introduction to Groupware Concepts

Groupware is the term used to define computer applications that enable people to share information over a distributed environment-a network. By this definition, current Internet mechanisms such as e-mail and the Web could be considered rudimentary groupware, because they do permit communication and dissemination of data. However, the general groupware concept extends beyond any existing Internet protocol and, in fact, encompasses multiple protocols simultaneously.

The key to groupware is focused on the ability for a collection of users to work with, add to, and modify a collection of data, with the changes taking place in real time. To affect this, a server application, which serves up the data from the database, and a client program, which interprets the data for display, need to work in tandem. In the past, creating groupware applications involved the development of complex and expensive server- and client-side applications, as well as having to port these applications to every operating system where a client or server was needed.

Because groupware relies on client-server communications, using the Web as the underlying protocol is a logical choice. The browser and Web server already have the necessary connection mechanism. All that's needed is some programming to handle data stored in a manner other than HTML. The Web White Board plug-ins for Netscape Navigator and Internet Explorer are examples of such programming: users all work on the same graphic on the board, with each user's changes being reflected to the others. In addition, the white board supports a text-based chat channel that permits typed messages to be reflected to all members in the group. Figure 22.1 is an example of CoolTalk, Netscape's Internet phone plug-in that includes a Web-based white board.

Figure 22.1 : Netscape's CoolTalk allows users to work on the same white board graphics, making notations and exchanging information in real time.

With this in mind, you may recognize the guestbook and graffiti wall scripts (covered back in chapter 12, "Guestbook/Grafitti Wall") as examples of groupware. In fact, the basic script concepts presented there can be adapted to yet another application: a groupware calendar.

Basic Functions Needed in a Calendar Program

Hard-core calendar programs usually require a copy of the software to be installed on every computer that needs access to the calendar, as well as a copy on the server, in the case of client-server systems. Maintenance, at this point, can become a major headache, which is why the Web's flexibility is such a boon. With a Web-based calendar, no additional software (apart from the browser) is required on the user's workstation, nor is any special software needed on the server. All maintenance and manipulation can be taken care of through a collection of CGI scripts and the familiar Web interface.

The basic requirements of any calendar program are the following:

With these things in mind, the construction of the calendar itself becomes somewhat academic.

Designing a Web-Based Calendar

The toughest part of calendar design is trying to decide how to structure the database. The event database needs to be both flexible and space-conscious-it must not take up enormous quantities of disk space.

A clean, logical structure would be the following:

To make the system even simpler, let the directory names correspond to what they contain. In that manner, you can use the data returned from parsing dates to point directly to the correct folder. Figure 22.2 is an example of such a directory tree.

Figure 22.2 : By using the calendar year and month as directory names, you create a structure that makes sorting and maintaining the calendar much easier.

You'll notice that the top-level directory "calendar" isn't at the root of the file system (/), meaning that it can be placed anywhere. Two good possible locations are:

  1. As a directory below the cgi-bin directory of your Web server. That way, the CGI script can be placed within the "calendar" directory to help organize your CGI directory, and can access the database relative to where the script is located.
  2. In a separate directory kept somewhere in conjunction with the Web server document tree (that is, in the same directory the server's "htdocs" directory is located). This is beneficial if you're running a site that hosts multiple domains, as it gives each domain its own calendar database without having to customize the database structure further.

Because each file-such as calendar/1996/January/22.dat for January 22, 1996-has events for only one day, storing the information can be done using the same database tricks shown in previous chapters-use a flat text file for the database file, one record per line, with individual fields separated by a delimeter.

TIP
For more background on databases, check out chapter 15, "Managing a Database."

Controlling Access to Calendar Features

Those who utilize the calendar can be divided into the following two groups:

  1. Administrators-Those who have permission to schedule, reschedule, or delete individual events.
  2. End users-Those who only have permission to view the stored data.

In some situations, it's feasible to give end users administrator access and let them maintain the calendar themselves. However, in most cases, having someone ultimately responsible for maintaining the group's information is preferable. This is especially important when individuals within the group depend on the central calendar to set their own calendars. If you want to restrict administrative access to a few select people, the easiest way is to utilize password control. With server-level password control, you create a server control file, such as .htaccess, which points to a file, like .htpasswd, that contains authorized user names and passwords.

TIP
For more information on password access control, check out chapter 6, "Controlling User Access."

Regardless of whether you choose to assign an administrator or let everyone collectively maintain the calendar, the underlying mechanism is basically the same, as follows:

Displaying a Month

The simplest way to display a month is as a table, like the calendar in figure 22.3 from the University of Dayton Student Government Association (http://www.udayton.edu/~sga/Calendar/calendar.cgi?calendar=sga). The hardest parts of creating the display are determining the following:

Figure 22.3 : An example of a Web-generated calendar in action. The day values in each table cell are hyperlinks that take the surfer to a page that lists the day's activities and allows for adding/changing entries.

How many days are there in a given month? (Remember to account for Leap Year, if necessary.)
What day of the week does the first of the month fall on?
What days within the given month have associated events?

Leap Year  Testing for Leap Year is straightforward, after you realize that Leap Years happen in years that are evenly divisible by 4. The Perl modulus operator (%), which returns the remainder of an integer division, can therefore be used to determine if February has 28 or 29 days:

if (!($year % 4)) {
   // 29 days
} else {
   // only 28
}

The rest of the year is most easily handled by an array:

@Days = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );

To associate a particular element in NumDays to the corresponding month, you can either convert the month to a numeric value-0 for January, 1 for February, and so on-or you can convert the list to a named list. Simply define an array:

@Months = ("January", "February", "March", ...);

and search the list for a month match. The index of the month is the index into the Days array:

for ($month=0; $month<12; $month++) {
   if ($strMonth =~ /$Months[$month]/) {
      $numDays = $Days[$month];
      break;
   }
}

A side effect of this loop is that $month now holds the numeric equivalent of the given month-0 for January, 1 for February, and so on.

Julian Dates  Identifying where the first of a month falls in the week is best done by converting the date to Julian format, where a particular date is converted to the number of days since January 1, 4713 B.C., which is demonstrated in listing 22.2.


Listing 22.2  Julian Dates
sub Julian {
   local($m, $d, $y) = @_;
   local($ya, $c);

   $y = (localtime(time))[5] + 1900  if ($y eq '');

   if ($m > 2) {
      $m -= 3;
   } else {
      $m += 9;
      --$y;
   }

   $c  = int($y/100);
   $ya = $y - (100 * $c);
   $jd =  int((146097 * $c) / 4) +
          int((1461 * $ya) / 4) +
          int((153 * $m + 2) / 5) +
          $d + 1721119;

   $jd;
}

From this function, the day of the week for September 7, 1970 would be computed by:

$dayofWeek = &Julian(9, 7, 1970);

Locating Event Files  Each day's events are stored in a separate file within the appropriate month's directory. To see if a particular day has any associated events, you can use Perl to test for the existence of the day's event file:

if (-e "calendar/$year/$strMonth/$day.dat") {
   // Day has events
} else {
   // No events for given day
}

Building the Table  Putting this all together yields the code fragment in listing 22.3, which generates a month display. Days that have associated events are hyperlinks to an Events for the Day display. A counter, $currDOW, is used to keep track of what day of the week is being formatted, and to start a new row when necessary.


Listing 22.3  Displaying a Month
print "<TABLE BORDER=2>\n";
print "<TR>\n";
print "<TH COLSPAN=7>$strMonth $year</TH>\n";
print "</TR><TR>\n";
print "   <TH>Su</TH><TH>Mo</TH><TH>Tu</TH><TH>We</TH>\n";
print "   <TH>Th</TH><TH>Fr</TH><TH>Sa</TH>\n";
print "</TR>\n";

$firstDay = (&Julian($month, "1", $year) + 1) % 7;

print "<TR>\n";

for($currDOW=0; $currDOW<$firstDay; $currDOW++) {
   print "<TD></TD>\n";
}

for ($day=1; $day<=$numDays; $day++) {
   if($currDOW > 6) {
      print "</TR>\n<TR>";
      $currDOW = 0;
   }

   $hasEvents = (-e "calendar/$year/$strMonth/$day.dat");

   print "</TD>";

   if ($hasEvents) {
      print "<A HREF=\"/cgi-bin/calendar\.pl?$year+$strMonth+$day\">";
   }

   print "$day";

   if($hasEvents) {
      print "</A>";
   }

   print "</TD>";

   $curDOW++;
}

print "</TR></TABLE>";

Displaying a Day

With the month displayed to the user, displaying the events for a given date is no different from displaying the contents of a guestbook. Each line in a date file has one event,
consisting of the following:

Which day to display is controlled by the way the calendar script is called. Remember the link created in listing 22.3:

<A HREF="/cgi-bin/calendar.pl?$year+$month+$day">$day</A>

The Perl variables $year, $month, and $day are replaced with their contents, as in:

<A HREF="/cgi-bin/calendar.pl?1970+September+7">7</A>

The values 1970, September, and 7 are passed to the calendar.pl script as parameters, and can be retrieved using the Perl $ARGV[], as in:

$year     = $ARGV[0];
$strMonth = $ARGV[1];
$day      = $ARGV[2];

NOTE
This technique can be used with the month script from listing 22.3 by checking whether $ARGV[2] is defined. If it isn't defined, only the month is formatted:
if($day) {
// display day
} else {
// display month
}

After the date information has been retrieved, simply open the file and display the day's events. Listing 22.4 is an example of formatting a day's activities.


Listing 22.4  A Day's Schedule
print "<UL>\n";

open (DAY, "calendar/$year/$strMonth/$day.dat");

while (<DAY>) {
   ($time, $desc) = split (/\|/, $_);
   print "<LI><B>$time:</B> $desc</LI>\n";
}

close (DAY);

print "</UL>\n";

While the code in listing 22.4 only displays the time and description of each event, you can get as fancy as you want with your pages. For example, if you are letting users submit their own events to the calendar, you may want to display the name of the poster as well (see figure 22.4).

Figure 22.4 : With a little creative table formatting-the event header line in the middle of the page uses colored table cells, and some added information from the poster-you can create as visually exciting a calendar as you want.

Performing Calendar Maintenance

Displaying a calendar of events is only part of the job-the other half being maintaining (add, deleting, and editing events) the calendar. Extending the scripts demonstrated so far to permit maintenance can be broken down into the following three distinct functions:

  1. Adding new events
  2. Editing existing events
  3. Deleting events

All of these functions can be easily modeled after the guestbook/graffiti wall scripts from chapter 12. One way of handling deletion simply is to wrap the day's events display within a form, associating a check box with each event.

Listing 22.5 is an example of a Perl script, called maintain.pl on the companion CD-ROM, that handles deleting events.


Listing 22.5  Deleting Events (Part 1)
print "Content-type: text/html\n\n";

print "<FORM METHOD=POST ACTION=\"/cgi-bin/maintain.pl\">\n";

print "<INPUT TYPE=HIDDEN NAME=\"year\" VALUE=\"$year\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"month\" VALUE=\"$strMonth\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"day\" VALUE=\"$day\">\n";

print "<CENTER><TABLE BORDER=3>\n";
print "<TR><TH>Delete</TH><TH>Event</TH></TR>\n";

open (DAY, "calendar/$year/$strMonth/$day.dat");

$i = 1;

while (<DAY>) {
   ($time, $desc) = split (/\|/, $_);
   print "<TR>\n";
   print "<TD><INPUT TYPE=CHECKBOX NAME=\"Kill-$i\" VALUE=\"yes\"></TD>\n";
   print "<TD><B>$time:</B> $desc</TD></TR>\n";

   $i++;
}

close (DAY);

print "</TABLE>\n";
print "<INPUT TYPE=SUBMIT Value=\"Delete Selected Items\">";
print "<INPUT TYPE=RESET Value=\"Clear Fields\">";
print "</CENTER></FORM>";

This script works a bit differently from the previous listings in this chapter. When dealing with form transmittal, the data that comes through is passed as part of the CONTENT_LENGTH environment variable. Therefore, it's necessary to define three hidden fields to identify the day, month, and year, so that when the script goes into maintenance mode, it knows which file to edit.

When the form is submitted, any events flagged for deletion are indicated by the presence of a kill-i field within CONTENT_LENGTH, where i is a number from 1 to the number of events. For example, if the fourth event of the day was flagged for deletion, CONTENT_LENGTH would contain the following somewhere within its length:

...Kill-4=yes...

The other side of the delete handler is what happens after the form is submitted. Then, in sequence:

  1. Retrieve the form data from CONTENT_LENGTH.
  2. Determine the correct event file and open it.
  3. Load its data into memory.
  4. Write the data back out to disk, omitting the events flagged for deletion.

Listing 22.6 demonstrates this part of the process.


Listing 22.6  Deleting Events (Part 2)
# retrieve the form fields
#
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
   read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
   @pairs = split(/&/, $buffer);

   foreach $pair (@pairs) {
      ($name, $value)  = split(/=/, $pair, 2);
      $tname = $name;

      $contents{$name} = $value;
   }
}

# determine the correct event file
#
$dayFile = "calendar/$contents{'year'}/$contents{'month'}/$contents{'day').dat";

# open the event file and load it's contents into memory.
#
open(DAY, "$dayFile");
$i = 1;
while(<DAY>) {
   chop;
   $events{$i} = $_;
   $i++;
}
close(DAY);

# Now, open the file for writing and write out the non-deleted
# fields.
#
open(DAY, ">$dayFile");

for($j=1; $j<=$i; $j++) {

   # check for the presence of a kill-flag (rather, the 
   # absence of one).
   #
   if(!(defined $contents{"Kill-$j"})) {
      print DAY "$list{$j}\n";
   } 
} 

close(DAY);

exit;

The testing for this block actually works in reverse: if a line isn't flagged for deletion, it's written out.

This system can be extended to enable easy maintenance of months or years, simply by adjusting the form and checking for the presence of the various hidden fields. For example, to edit the schedule at the month level, perhaps to delete an entire day, follow these steps:

  1. Modify the month display script to include Delete This checkboxes for days with events.
  2. Omit the day hidden field within the form.
  3. Test for the existence of the day field within the script. If the field is missing, you want to edit a month. If the field exists, you want to edit a day.

Follow these same guidelines to extend this up to include months.

Adding Calendar Enhancements

While the calendar system presented here is a simple one, it provides a basis on which you can easily expand in a variety of directions. For example, the following are a few ideas:

Both Navigator and Explorer support setting the background color of individual table cells through the BGCOLOR attribute of the <TD> tag. This feature can be used to color code the displayed calendar to highlight important dates.
If the amount of information you store within the calendar is relatively small, it may be worthwhile to format an entire month's data into a JavaScript array that's passed back to the browser for processing and display. This speeds up the user's browsing through a given month's activities.
Extend the administrative interface to support "multi-day" events, such as holidays or week-long seminars, perhaps by having a separate file for events that extend over a range, or by entering the event in each day's database. A multi-day event is one that extends over several days.
By referencing a database within the user's personal directory, the calendar interface can be extended to include a user's own data files. This allows users to maintain their own schedules within the context of the company as a whole, without directly interfering with the main database.

TIP
If you need a "kick in the right direction" on implementing some of these ideas, check the CD-ROM. You'll find examples of each of these and other tricks

From Here…

For more information on the underlying constructs that control the groupware calendar, and to look at related examples of groupware scripting, check out the following: