reptile7's JavaScript blog
Friday, April 13, 2012
 
Calendar Coding Girl or Boy
Blog Entry #247

In today's post we will take up the Calendar snippet of HTML Goodies' "Top 10 JavaScript Snippets for Common Tasks" tutorial. The tutorial specifies no authorship nor a source for the Calendar snippet (or for any of the other snippets, for that matter), but a Google search led me to a JavaScript Source page that credits the snippet to Alexander Babichev.

Do you buy a 16-month calendar every December?


The Calendar snippet codes a 60-month calendar. Via two selection lists, the calendar provides access to all twelve months of the current year, the next two years, and the two preceding years, and the year range can be expanded if desired. The calendar is self-renewing: without making any changes, it will work 5, 10, 20 years down the line. And it is easily placed in the future or past; it can be used to create a 1941-1945 calendar, for example.

The calendar code's use of id attribute values as object references implies that the code was originally written for Internet Explorer; however, we recently noted in Blog Entry #244 that this practice is now legit for all of the major browsers. The snippet runs smoothly with the various OS X GUI browsers on my computer excepting IE 5.2.3, and a very minor change allows it to work with IE 5.2.3 as well.

Overview

As you might guess, the calendar has a tabular layout.
• The calendar is framed by a two-row outer table.
• The outer table's first row contains a table housing the month/year selection lists.
• The outer table's second row contains a table comprising a 7-by-7 grid of cells that hold the calendar's day/date data.
None of these tables has an identifier, so let's respectively call them tables[0], tables[1], and tables[2], as though we were getting them with a var tables = document.getElementsByTagName("table"); command.

Unlike most block-level elements, a table element has a shrink-to-fit width (by default, unless you set it otherwise). The width of the calendar is effectively set by the tables[2] table; the tables[1] table is given a width='100%' attribute so that its width is equal to the tables[2] table width.

The calendar's outermost element is actually a form element, whose only role is to provide a name='calForm' attribute for accessing the month/year selection lists via document.calForm.selectName expressions. The calForm form can be thrown out given that we can get the selection lists via getElementById( ) commands (or a getElementsByTagName("select") command if you prefer).

The snippet begins with a style element that applies various styles to the tables[2] day/date data. The style block is followed by a script element comprising four functions:

(1) A writeCalendar( ) function assembles the calendar markup as one long text string, which is written to the page via a document.write(text); command; there is no separation of structure and behavior in the snippet.

(2) A changeCal( ) function populates the lower six rows of the tables[2] table with dates for the current month or for the user's chosen month plus some grayed-out dates for the preceding and following months à la a normal calendar. (The tables[2] day heading row is filled by the writeCalendar( ) function.)

(3) The changeCal( ) function calls on a maxDays( ) function that determines the number of days in the current/chosen month and in the preceding month; this information is used to divide the lower six rows of the tables[2] table into zones for the current/chosen month, the preceding month, and the following month.

(4) A changeBg( ) function allows the user to highlight (give a yellow background color to) the dates in the lower six rows of the tables[2] table by clicking on them.

The snippet concludes with a second script element containing a writeCalendar( ) function call, which sets the snippet in motion.

<script type="text/javascript">writeCalendar( );</script>

Write it

The lines below get us to the first cell of the one-row tables[1] table, which holds the month selection list:

function writeCalendar( ) {
    ...
    var text = ""; /* This line can be deleted if we declare text on the next line. */
    text = "<form name='calForm'>";
    text += "<table border='1'>";
    text += "<tr><td>";
    text += "<table width='100%'><tr>";
    text += "<td align='left'>"; /* The align setting is unnecessary. */


The following code creates the month selection list:

var arrM = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
var now = new Date
var mm = now.getMonth( );
text += "<select name='selMonth' onchange='changeCal( );'>";
for (ii = 0; ii <= 11; ii++) {
    if (ii == mm) { text += "<option value='" + ii + "' selected>" + arrM[ii] + "</option>"; }
    else { text += "<option value='" + ii + "'>" + arrM[ii] + "</option>"; } }
text += "</select>";


• The arrM array's month elements map onto the 0-to-11 returns of the Date object's getMonth( ) method.
• The now Date object's constructor syntax is not standard (Date should be followed by parentheses) but it throws no errors with the browsers on my computer.
• The selection list is given a selMonth name and is bound to the changeCal( ) function via an onchange attribute.
• The first-in-source-order option reads January and has a value of 0; the next option reads February and has a value of 1; and so on. The current (mm) month is preselected via the for loop's if clause.

An adjoining tables[1] cell

text += "</td>";
text += "<td align='right'>";


holds the year selection list, which is created by the following code:

var yyyy = now.getFullYear( );
var arrY = new Array( );
for (ii = 0; ii <= 4; ii++) { arrY[ii] = yyyy - 2 + ii; }
text += "<select name='selYear' onchange='changeCal( );'>";
for (ii = 0; ii <= 4; ii++) {
    if (ii == 2) { text += "<option value='" + arrY[ii] + "' selected>" + arrY[ii] + "</option>"; }
    else { text += "<option value='" + arrY[ii] + "'>" + arrY[ii] + "</option>"; } }
text += "</select>";


• The arrY array holds five years centered around the current (yyyy) year and in chronological order, as though we had coded var arrY = [2010, 2011, 2012, 2013, 2014];.
• The selection list is given a selYear name and is bound to the changeCal( ) function via an onchange attribute.
• This being the year 2012, the first-in-source-order option reads 2010 and has a value of 2010; the next option reads 2011 and has a value of 2011; and so on. The current year is preselected.

By tweaking the above for loops you can expand the calendar year range. If you would like the calendar to have, say, a seven-year range centered around the current year, then
(1) iterate the ii counter from 0 to 6 in both loops,
(2) subtract 3 from yyyy in the first loop, and
(3) use an ii == 3 if condition in the second loop.
That's all there is to it.

Moving along, the lines below get us to the tables[2] table:

text += "</td>";
text += "</tr></table>"; // Closes the tables[1] table
text += "</td></tr>"; // Closes the first row of the tables[0] table
text += "<tr><td>";
text += "<table border='1'>";


The tables[2] table's first row holds the Sun-to-Sat day headings, and is written with:

var arrD = new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
text += "<tr>";
for (ii = 0; ii <= 6; ii++) { text += "<td align='center'><span class='label'>" + arrD[ii] + "</span></td>"; }
text += "</tr>";


• The arrD array's day heading elements map onto the 0-to-6 returns of the Date object's getDay( ) method.
• The align='center' cell alignment would be redundant if we were to mark up the cells as th elements, as we should.
• Each cell is equipped with a span element child having a class='label' CSS identifier. Here's the span.label rule set in the snippet's style block:

span.label { color: black; width: 30; height: 16; text-align: center; margin-top: 0; background: #fff; font: bold 13px Arial; }

Style bullet points:
• The span part of the selector is unnecessary.
• The width and height settings lack unit identifiers, which is illegal.
• More importantly, the CSS width and height properties do not apply to inline span elements. (Ah, but IE will let you set a CSS width and height for an inline span - this would seem to be the smoking gun that the snippet was indeed written for IE.)
The CSS text-align property doesn't apply to inline span elements either.
(The width, height, and text-align properties duly apply to display:inline-block; spans, but that's not what we've got.)
• The use of th cells would allow us to throw out the bold part of the font declaration.
• Lastly, I see no difference at all upon commenting out the margin-top: 0; declaration.

Is the span wrapper necessary in the first place? Nah - the color, width, height, background, and font settings can all be applied to the th parent(s).

The remaining 6 rows/42 cells of the tables[2] table are written by a 'two-dimensional loop':

aa = 0;
for (kk = 0; kk <= 5; kk++) {
    text += "<tr>";
    for (ii = 0; ii <= 6; ii++) {
        text += "<td align='center'><span id='sp" + aa + "' onclick='changeBg(this.id);'>1</span></td>";
        aa += 1; }
    text += "</tr>"; }


The align='center' cell alignment is all right here although I myself would specify this in the style block. Each cell is 'initialized' with a 1* unnecessarily wrapped in a span element. From left to right and top to bottom, the spans are given ordinalized ids: sp0, sp1, ... sp41. Each span is bound to the changeBg( ) function via an onclick attribute.

*FWIW: With IE 5.x for Mac, the attempted replacement of these 1s with the current month's dates gives a really strange display.
The Calendar snippet display with IE 5.2.3 for Mac
This is evidently a version bug as I am able to run the snippet with IE 4.5 in the SheepShaver environment without any problems; removing the 1s (leaving the cells empty to begin with) sorts out the situation.

We wrap up the writeCalendar( ) function by (1) closing the tables[2] table, the tables[0] table, and the calForm form, (2) writing the text string to the page, and (3) calling the changeCal( ) function.

text += "</table></td></tr></table></form>";
document.write(text);
changeCal( ); }


changeCal( ) intro

The changeCal( ) function begins by creating a now Date object and fetching now's getDate( ), getMonth( ), getDay( ), and getFullYear( ) returns.

function changeCal( ) {
    var now = new Date( );
    var dd = now.getDate( );
    var mm = now.getMonth( );
    var dow = now.getDay( );
    var yyyy = now.getFullYear( );


Actually, this same series of statements appears at the beginning of the writeCalendar( ) function. The above discussion attaches the now/mm and yyyy assignments to the writeCalendar( ) sections to which they are relevant; I didn't mention the dd and dow assignments because the writeCalendar( ) function makes no use of them. The changeCal( ) function doesn't act on the dow return either; it employs the now, dd, mm, and yyyy returns to lightgreen-highlight the current day, and that's it.

Anyway, if we were to specify the now, dd, mm, and yyyy assignments as top-level code

<script type="text/javascript">
var now = new Date( );
var dd = now.getDate( );
var mm = now.getMonth( );
var yyyy = now.getFullYear( );
function maxDays(mm, yyyy) {
    ...


then we would only have to write them out once. And once we've done that, then moving the calendar to the future or past is a simple matter of putting an appropriate Date.parse( )-compatible dateString into the now constructor, e.g.:

var now = new Date("Jan 1, 1943");

We'll get into the meat of the changeCal( ) function in the next entry.

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)