CS 371, Fall 2001
Lab 8: JavaScript

For this lab, you will do a little client-side Web programming with JavaScript. You will write a Web page that can display a one-month calendar for any month and for any year in a reasonable range of years. The calendar will be generated by a JavaScript script, and JavaScript will be used to change the displayed month. You can imagine a similar example in which the days of the calendar are links to something like a daily planner, but for today, you will just show the day numbers in a table. Of course, you could do much the same thing on the server side with PHP, but putting the script on the client side might give a better response.

The image at the right is a screen shot from my calendar web page. When the page is loaded with a URL of the form .../~jsmith/cal.html, it shows a calendar for the current month and current year. A different month and year can be provided as extra data in the query part of the URL (after a "?"). The month is given as an integer between 1 and 12, and the year is given as a positive integer. These are separated by an amphersand ("&"). So a URL of the form .../~jsmith/cal.html?2&1800 would produce a calendar for February, 1800. Note that this is not the usual form for the query part of a URL, which for a typical CGI application would probably be ?month=2&year=1800". However, we are not restricted to using the same syntax.

The scripting for this project uses several features that we haven't covered in class. I will cover them here.

Turn in a print-out of your HTML page before Thanksgiving break. Also put a link to your page on your main page, so that I can try it out on-line.


Creating a Calendar

The first step is to write the code to create a calendar for a given month and year. To keep this organized, this should be done by a JavaScript function. Functions are typically defined in a script in the <head> section of an HTML document. For now, you can simply test the function by putting a call to the function in the <body> of the page. It will be convenient to have an array that contains the names of the months. Note that the variables and functions that are defined in a script on one part of the page can be used in other scripts anywhere on the page. So, start work by creating an HTML document with the form:

       <html>
       <head>
       <title>Any Title You Like</title>
       <script language="JavaScript">
          var monthNames = ['January','February','March','April',
                            'May','June','July','August','September',
                            'October','November','December'];
          function makeCal(month,year) {
               // Make a calendar for the specified month and year.
               // Month is an integer in the range from 1 to 12.
               // Year is some reasonable positive integer.
          
          } 
       </script>
       </head>

       <body>
       <script>
          makeCal(11,2001)
       </script>
       </body>
       </html>

You just have to fill in the function. This is not a trivial problem. For November, 2001, makeCal must generate something like the following HTML code:

    <table align=center cellpadding=7 border=1 bgcolor='#DDDDDD'>
    <tr>
    <td align=center bgcolor='#CCCCFF' colspan=7><big>
         November, 2001</big></td>
    </tr>
    <tr bgcolor='#DDDDFF'>
    <td>Su</td><td>Mo</td><td>Tu</td><td>We</td><td>Th</td><td>Fr</td><td>Sa</td>
    </tr>
    <tr>
    <td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>1</td><td>2</td><td>3</td>
    <tr>
    <td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td>
    </tr>
    <tr>
    <td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td>
    </tr>
    <tr>
    <td>18</td><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td>
    </tr>
    <tr>
    <td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>&nbsp;</td>
    </tr>
    </table>

The "&nbsp;'s" are non-breaking spaces that act as placeholders in otherwise empty cells.

After outputting the first two rows of the table, your function can use nested loops to output the rest of the table. An inner for loop that runs from 1 to 7 can generate one row of the table. This can be inside an outer while loop that runs through the weeks of the month until it runs out of days. The hard parts are deciding how many days there are in the month and which weekday the first of the month falls on.

To get the last day of the month, you just have to test which month it is and, if it is February, whether or not it is a leap year. Rather than make you think too hard about this, here is the code:

      var lastDay;
      if (month == 4 || month == 6 || month == 9 || month == 11)
         lastDay = 30;
      else if (month == 2) {
         if (year % 400 == 0)
            lastDay = 29;
         else if (year % 100 == 0)
            lastDay = 28;
         else if (year % 4 == 0)
            lastDay = 29;
         else
            lastDay = 28;
      }
      else
         lastDay = 31;

The leap year code might not be exactly what you expect, but it is correct for the Gregorian calendar, which has been in use for several hundred years.

It would be hard to compute the first weekday of the month, but fortunately we can get JavaScript to do it for us by using the JavaScript Date class. The default constructor for this class ("new Date()") creates a Date object that represents the current time. But there is another constructor that can be used to generate a Date object for any specified time. Specifically,

         var firstOfMonth = new Date(year, month-1, 1)

represents the first day of the specified month and year. You have to use month-1 because for the purposes of this constructor, months are numbered from 0 to 11 rather than from 1 to 12. Now, a Date object has a method that can be used to determine what day of the week the date falls on. The method call

         var day = firstOfMonth.getDay()

returns a value in the range 0 to 6. This value tells you whether the first of the month is a Sunday or a Monday or.... For November, 2001, day is 4 since the first day of the month is Thursday. You can use this information to determine how many blank cells to output before starting to output dates. Here is a suggestion on an easy way to do this. Compute

         day = 1 - day;

This is the negative number that you get when you start from the first day of the month and count backwards to Sunday. If you just went ahead and started printing date numbers with this value, the calendar would look something like

            -3 -2 -1  0  1  2  3
             4  5  6  7  8  9 10
            11 12 13 14 15 16 17
            18 19 20 21 22 23 24
            25 26 27 28 29 30

The trick is to do this, but when the date number is less than or equal to zero, output &nbsp; instead of the number.

You should be able to complete the makeCal() function and test it by loading your page in a browser. You might want to make your function do something simpler for now and do the rest of the lab first.


Getting Month and Year from URL

The next step is to change the little script in the body of your page so that it will get the month and year out of the document's URL. The URL of the current Web page is represented by the standard JavaScript variable location. This variable has properties that represent various parts of the URL. In particular, location.search is the query part of the URL, including the "?". In our example, we expect the value of this property to be either empty or something of the form "?2&1800". If it's empty, you want to print a calendar for the current month and year. These can be obtained from a Date object:

          today = new Date();   // represents current date.
          month = today.getMonth() + 1;  // Number between 1 and 12.
          year = today.getFullYear();    // Four-digit year number.

If location.search is non-empty, you have to extract the month and the year from it. Unfortunately, JavaScript doesn't do this for us automatically the way PHP does. You have to parse the string yourself. To do this, I used a substring function to get rid of the "?":

          data = location.search.substr(1);

Then I used the nifty "split()" method to break the string into parts. This function takes a string and a separator character. It breaks the string into substrings separated by the given character. For example, if "," is the separator, it would break "One,Two,Three" into the three strings "One" "Two" "Three". The separated strings are returned as an array. In this application, "&" is the separator:

           monthAndYear = data.split("&");

We expect to get an array of length 2 where monthAndYear[0] is the month and monthAndYear[1] is the year for which we want to print a calendar.

If you want to do some error checking, note that monthAndYear.length gives the length of the array. If you want to check whether the values provided are actually legal integer values, you could use the built-in parseInt(str) function. This function returns the integer value represented by a string. If the string does not hold a number, it returns the special value NaN. You can check whether the returned value is NaN by calling the built in function isNaN(val).


Changing the Display

The third and final part of the project is to make a form that can be used to select a different month and/or year. My form looks like this boring (non-functional) form

Select Another Calendar
Month:
Year:
 

You could look at the source code of this page to see how I did this, if you want. You want to script the button so that it reloads the page with a new URL containing the selected month and year. The typical way to script a button is to specify a JavaScript function as the onclick event handler for the button:

    <input type=button value="Click here" onclick="newCal()">

Then you can define the specified function, newCal(), in the script in the <head> of the document. In this case, the function has to get the month and year from the form elements. To make this easy, the form and form elements should have name attributes. Then the values of the elements can be referred to with names of the form

        document.formName.selectName.selectedIndex

for the select menu and

        document.formName.inputName.value

for the input box.

An interesting question is how to get the page to reload with the new month and year. The location object has a method for doing just this. You can call location.replace(url) to replace the current page with the specified URL. In this case, you just have to construct a URL with the specified month and year encoded in the query part of the URL.

(Actually, should just be able to do an assignment: location.search = "?" + month + "&" + year, and that should cause the page to reload. This worked in Netscape and Mozilla, but not in some other browsers that I tested. Anyway, this sort of thing still seems sort of wierd to me.)


Extensions

It would be fairly simple to extend this concept to produce a calendar for an entire year. (Perhaps you could do this if the URL contains only one number.) All you have to do is make a big table with, say, three columns and four rows. In each cell of the big table put a one-month calendar created by your makeCal() function.

Perhaps more interesting would be replacing the "Month, Year" label at the top of the calendar with controls for changing the month and year. This would be a form with two <select> elements that control the month and the year. You could script the onchange event of the <select> elements so that when the user changes either selection, a new page is loaded to show the new calendar. You will have to provide a fairly limited range of years, since each year will appear in the menu. Whenever a page is loaded, you would want the month and year selected in the menus to be the same month and year for which the calendar is displayed. You could do this by using a script to generate the menus, so that you can stick a selected attribute in the correct <option>. Alternatively, you could put a small script at the end of the page to set the values of the <select> menus.

These extensions are not required. Perhaps you will get extra credit if you do them.


David Eck, 9 November 2001