From the Web:

James Thiele's Reminder Calendar


James Thiele has designed a simple reminder calendar as an example of using cookies in JavaScript. The calendar is available at

http://www.eskimo.com/~jet/javascript/calendar_js.html

Rather than being a full-fledged application, this page provides simple functionality, yet is a compelling example of how cookies can extend the value of a script beyond time and session constraints.

The program (see Listing W4.1) displays a calendar of the current month, much the same as in Dave Eisenberg's calendar example. When users click on any of the days in the calendar, they either can enter a reminder for that day, or are alerted of their previously entered reminder.

When the user reloads the page or returns to it later in the month, the days with reminders are displayed in a different color.


Listing W4.1. Source code for James Thiele's Reminder Calendar.
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">


<!--  to hide script contents from old browsers

//
//  Cookie Functions
//  Written by:  Bill Dortch, hIdaho Design
//  The following functions are released to the public domain.
//

//
// "Internal" function to encode cookie value.  This permits cookies to
// contain whitespace, comma and semicolon characters.
//
function encode (str) {
  var dest = "";
  var len = str.length;
  var index = 0;
  var code = null;
  for (var i = 0; i < len; i++) {
    var ch = str.charAt(i);
    if (ch == " ") code = "%20";
    else if (ch == "%") code = "%25";
    else if (ch == ",") code = "%2C";
    else if (ch == ";") code = "%3B";
    else if (ch == "\b") code = "%08";
    else if (ch == "\t") code = "%09";
    else if (ch == "\n") code = "%0A";
    else if (ch == "\f") code = "%0C";
    else if (ch == "\r") code = "%0D";
    if (code != null) {
      dest += str.substring(index,i) + code;
      index = i + 1;
      code = null;
    }
  }
  if (index < len)
    dest += str.substring(index, len);
  return dest;
}

//
// "Internal" function to decode cookie values.
//
function decode (str) {
  var dest = "";
  var len = str.length;
  var index = 0;
  var code = null;
  var i = 0;
  while (i < len) {
    i = str.indexOf ("%", i);
    if (i == -1)
      break;
    if (index < i)
      dest += str.substring(index, i);
    code = str.substring (i+1,i+3);
    i += 3;
    index = i;
    if (code == "20") dest += " ";
    else if (code == "25") dest += "%";
    else if (code == "2C") dest += ",";
    else if (code == "3B") dest += ";";
    else if (code == "08") dest += "\b";
    else if (code == "09") dest += "\t";
    else if (code == "0A") dest += "\n";
    else if (code == "0C") dest += "\f";
    else if (code == "0D") dest += "\r";
    else {
      i -= 2;
      index -= 3;
    }
  }
  if (index < len)
    dest += str.substring(index, len);
  return dest;
}

//
// "Internal" function to return the decoded value of a cookie
//
function getCookieVal (offset) {
  var endstr = document.cookie.indexOf (";", offset);
  if (endstr == -1)
    endstr = document.cookie.length;
  return decode(document.cookie.substring(offset, endstr));
}

//
//  Function to return the value of the cookie specified by "name".
//    name - String object containing the cookie name.
//
function GetCookie (name) {
  var arg = name + "=";
  var alen = arg.length;
  var clen = document.cookie.length;
  var i = 0;
  while (i < clen) {
    var j = i + alen;
    if (document.cookie.substring(i, j) == arg)
      return getCookieVal (j);
    i = document.cookie.indexOf(" ", i) + 1;
    if (i == 0) break;
  }
  return null;
}

//
//  Function to create or update a cookie.
//    name - String object object containing the cookie name
//    value - String object containing the cookie value.  May contain
//      any valid sting characters, including whitespace, commas and quotes.
//    expires - Date object containing the expiration date of the cookie,
//      or null to expire the cookie at the end of the current session.
//
function SetCookie (name, value, expires) {
  document.cookie = name + "=" + encode(value) + ((expires == null) ? "" :
     Â("; expires=" + expires.toGMTString()));
}

//  Function to delete a cookie. (Sets expiration date to current date/time)
//    name - String object containing the cookie name
//
function DeleteCookie (name) {
  var exp = new Date();
  var cval = GetCookie (name);
  document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();
}


function intro()
{
   document.write  ("<CENTER>");

   document.writeln("<BR>");
   document.write  ("<H1>");
   document.write  ("Reminder Calendar");
   document.writeln("</H1>");
   document.writeln("</CENTER>");
   document.writeln("<h2>How to use the Reminder Calendar:</h2>");
   document.writeln("<ul><li>Click on a  date to add a reminder");
   document.writeln("    <li>Click on that date again to see the reminder");
   document.writeln("    <li>Reload the page to see dates with
       Âreminders in different colors");
   document.writeln("</ul>");

   document.writeln("<h2>Notes:</h2>");
   document.writeln("<ul><li>Lame user interface");
   document.writeln("    <li>Can't delete a reminder");
   document.writeln("    <li>Reminders disappear in about 24 hours");
   document.writeln("</ul>");

   document.writeln("<h2>This is mostly a programming example of:</h2>");
   document.writeln("<ul><li>Using cookies in JavaScript");
   document.writeln("    <li>Using text links to call a
       Âfunction, not open a URL");
   document.writeln("</ul>");

   document.writeln("<h2>Credits:</h2>");
   document.writeln("<ul><li>Cookie Functions written by:  
       Â<A href='mailto:bdortch@netw.com'>Bill Dortch</A>, hIdaho Design");
   document.writeln("<ul>The functions are at:");

   document.writeln("    <li>Code: <A href='http://www.hidaho.com/cookies/
cookie.txt'> http://www.hidaho.com/cookies/cookie.txt</A>");
   document.writeln("    <li>Demo: <A href='http://www.hidaho.com/cookies/
       Âcookie.html'> http://www.hidaho.com/cookies/cookie.html</A>");
   document.writeln("</ul>");
   document.writeln("    <li>Reminder Calendar by
       ÂJames Thiele who can be reached:");
   document.writeln("    <UL><LI>at his home page ");
   document.writeln("         <A href='http://www.eskimo.com/~jet'>
       Âhttp://www.eskimo.com/~jet</A>");
   document.writeln("        <LI> via email at ");
   document.writeln("         <address><A href='mailto:jet@eskimo.com'>
       Âjet@eskimo.com</a></address></p>");
   document.writeln("</ul>");
}


function arrayOfDaysInMonths(isLeapYear)
{
   this[0] = 31;
   this[1] = 28;
   if (isLeapYear)
                this[1] = 29;
   this[2] = 31;
   this[3] = 30;
   this[4] = 31;
   this[5] = 30;
   this[6] = 31;
   this[7] = 31;
   this[8] = 30;
   this[9] = 31;
   this[10] = 30;
   this[11] = 31;
}

function daysInMonth(month, year)
{
                                        // do the classic leap year calculation
   var isLeapYear = (((year % 4 == 0) &&
       Â(year % 100 != 0)) || (year % 400 == 0));
   var monthDays  = new arrayOfDaysInMonths(isLeapYear);

   return monthDays[month];
}


function calendar()
{
   var monthNames = "JanFebMarAprMayJunJulAugSepOctNovDec";
   var today      = new Date();
   var day        = today.getDate();
   var month      = today.getMonth();
   var year       = today.getYear() + 1900;

   // figure out how many days this month will have...
var numDays    = daysInMonth(month, year);

   // and go back to the first day of the month...
   var firstDay   = today;
       firstDay.setDate(1);
   // and figure out which day of the week it hits...
   var startDay = firstDay.getDay();

   var column = 0;

   // Start the calendar table
   document.write("<CENTER>");
   document.write("<TABLE BORDER>");
   document.write("<TR><TH COLSPAN=7>");
   document.write(monthNames.substring(3*month, 3*(month + 1)) + " " + year);
   document.write("<TR><TH>Sun<TH>Mon<TH>Tue<TH>Wed<TH>Thu<TH>Fri<TH>Sat");

   // put blank table entries for days of week before beginning of the month
   document.write("<TR>");
   for (i=1; i < startDay; i++)
   {
      document.write("<TD>");
      column++;
   }

   for (i=1; i <= numDays; i++)
   {
      // Write the day
      var s = "" + i;
      if ((GetCookie("d"+i) != null))
        // s = s.fontcolor(document.vlinkColor);
        s = s.fontcolor("#FF0000");
      s = s.link("javascript:dayClick(" + i + ")")
          document.write("<TD>" + s);

      // Check for end of week/row
      if (++column == 7)
      {
         document.write("<TR>"); // start a new row
         column = 0;
      }
   }
   document.write("</TABLE>");
   document.writeln("</CENTER>");
}


////////////////////////////
//////// dayClick //////////
////////////////////////////
function dayClick(day)
{
        var expdate = new Date ();
            expdate.setTime (expdate.getTime() + (24 * 60 * 60 * 1000));
            Â// 24 hrs from now
        var prefix                = "d";
        var theCookieName         = prefix + day;
        var theDayclickedReminder = GetCookie(theCookieName);

    if (theDayclickedReminder != null) {
        alert("The reminder for day " + day + " is:"  + theDayclickedReminder);
    } // end if

        if (confirm("Do you wish to enter a reminder for day " +
            Âday + " of this month?"))
        {
                x = prompt("Enter a reminder for day "+ day +
            Â" of this month", theDayclickedReminder);
        SetCookie (theCookieName, x, expdate);
    } // end if
}


// --> <!-- end hiding contents from old browsers  -->

</SCRIPT>

<TITLE>James Thiele's Calendar reminders
</TITLE>
</HEAD>

<BODY>

<SCRIPT LANGUAGE="JavaScript">


<!--  to hide script contents from old browsers

// Write the intro
// Write the calendar
calendar();
document.write("<HR>");
intro();
// --> <!-- end hiding contents from old browsers  -->

</SCRIPT>

<IMG SRC="../RainbowLine.gif">
<A href="index.html"><IMG SRC="javascriptlogo.gif">To JavaScript stuff</A>
<br><em>Page last modified 24 Jan 96</em>

</BODY>
</HTML>

The results look like Figures W4.1 and W4.2.

Figure W4.1 : A reminder prompt dialog appears when users click a date for the first time.

Figure W4.2 : In future visits, the days with reminders are displayed in different color.

The first thing you notice is that Thiele is using an early version of Bill Dortch's cookie function set. This version was written before JavaScript included the escape() and unescape() functions, which forced Dortch to write his own functions, encode() and decode(), to perform the encoding of values.

In any case, the newer version of Dortch's functions is backward compatible and could replace the old version in this script without affecting its operation in newer browsers.

Thiele has written five functions to implement his calendar program: intro(), arrayOfDaysInMonth(), daysInMonth(), calendar(), and dayClick().

In addition, he builds most of the body of his HTML document using a JavaScript script:

<SCRIPT LANGUAGE="JavaScript">


<!--  to hide script contents from old browsers

// Write the intro
// Write the calendar
calendar();
document.write("<HR>");
intro();
// --> <!-- end hiding contents from old browsers  -->

</SCRIPT>

This script is quite simple: it calls calendar() to display the calendar, it draws a horizontal line, and then it calls intro(), which displays most of the rest of the text of the script.

The intro() Function

This function needs little discussion. It is just a static collection of document.write() statements that output the introductory information about the application.

The arrayOfDaysInMonth() Function

The arrayOfDaysInMonth() function simply creates a twelve-element array containing the number of days in each calendar month. It accepts one Boolean variable as an argument. This enables it to correctly set the number of days in February based on whether or not it is a leap year.

The daysInMonth() Function

This function takes two arguments-the month and year-and uses these to determine if it is a leap year. It then creates an array of days in each month of the particular year and returns the number of days in the specified month.

The calendar() Function

The calendar() function is where some of the more complex processing occurs.

   var monthNames = "JanFebMarAprMayJunJulAugSepOctNovDec";
   var today      = new Date();
   var day        = today.getDate();
   var month      = today.getMonth();
   var year       = today.getYear() + 1900;

   // figure out how many days this month will have...
   var numDays    = daysInMonth(month, year);

   // and go back to the first day of the month...
   var firstDay   = today;
       firstDay.setDate(1);
   // and figure out which day of the week it hits...
   var startDay = firstDay.getDay();

   var column = 0;

As you might expect, the function starts by setting up key variables for use throughout the function. These include a string of month names-the same technique you saw in Dave Eisenberg's calendar script.

The firstDay Date object is used to get the day of the week of the first day of the month and store it in startDay.

   // Start the calendar table
   document.write("<CENTER>");
   document.write("<TABLE BORDER>");
   document.write("<TR><TH COLSPAN=7>");
   document.write(monthNames.substring(3*month, 3*(month + 1)) + " " + year);
   document.write("<TR><TH>Sun<TH>Mon<TH>Tue<TH>Wed<TH>Thu<TH>Fri<TH>Sat");

   // put blank table entries for days of week before beginning of the month
   document.write("<TR>");
   for (i=1; i < startDay; i++)
   {
      document.write("<TD>");
      column++;
   }

After building the header of the table, which holds the calendar for the month, the for loop inserts blank cells for each of the unused days of the week before the first day of the month.

   for (i=1; i <= numDays; i++)
   {
      // Write the day
      var s = "" + i;
      if ((GetCookie("d"+i) != null))
        // s = s.fontcolor(document.vlinkColor);
        s = s.fontcolor("#FF0000");
        s = s.link("javascript:dayClick(" + i + ")")
          document.write("<TD>" + s);

      // Check for end of week/row
      if (++column == 7)
      {
         document.write("<TR>"); // start a new row
         column = 0;
      }
   }

Next, another for loop repeats for each day in the month. For each day, a cell is created and the number is displayed with a hypertext link to the URL javascript:dayClick(number). This is done by using the string's link method to add the URL. As you will see in Chapter 10, "Strings, Math, and the History List," if the link method is used, then the value of the string is surrounded by an appropriate <A> HTML container tag.

Note
Notice the use of the javascript: URL. This type of URL can be used to call a function in the current document. When used in the HREF attribute of the <A> tag, this is often an alternative to using the onClick event handler. It can also be used in the open location dialog box of the Navigator browser to test what a line of JavaScript code will evaluate to. In this case, the result of evaluating an expression is displayed in the browser window.

Similarly, the fontcolor() method adds the appropriate HTML tags to the value of the string. In this case, the fontcolor() method is called only if the cookie for that day has been previously set with a reminder.

The final if statement checks whether you have reached the last column, and if so, closes the row, opens a new row in the table, and resets the column counter.

The dayClick() Function

This function handles both prompting for a reminder and displaying existing reminders. It is invoked when the user clicks on a date. It accepts the date as an argument.

        var expdate = new Date ();
            expdate.setTime (expdate.getTime() + (24 * 60 * 60 * 1000));
                     
// 24 hrs from now
        var prefix                = "d";
        var theCookieName         = prefix + day;
        var theDayclickedReminder = GetCookie(theCookieName);

The function starts by setting up its variables, including an expiry date for cookies and a value of the cookie for the selected date.

    if (theDayclickedReminder != null) {
        alert("The reminder for day " + day + " is:"  + theDayclickedReminder);
    } // end if

If the value of the cookie is not null, then the reminder is displayed in an alert dialog box.

        if (confirm("Do you wish to enter a reminder for day " +
            
day + " of this month?"))
        {
                x = prompt("Enter a reminder for day "+ day +
                                  
" of this month", theDayclickedReminder);
        SetCookie (theCookieName, x, expdate);
    } // end if

If the value of the cookie is null, then users are asked if they wish to enter a reminder for the current day and, if they do, they are further prompted for the text of the reminder. The reminder is then stored in a cookie. Notice the use of the confirm() call as the condition of the if statement-confirm() returns a value of true or false based on the user's response.