reptile7's JavaScript blog
Monday, March 30, 2015
Beneath the Headers I
Blog Entry #347

Let's get back now to our ongoing analysis of the Java Goodies Calendar and Datebook script and its ampCalendar_Display( ) function. With the calTable table's caption and header row in place

September 1998 - Eat at Joe's

we turn our attention to the table's data rows in this and the next posts.

More table loops

The remaining rows are written out by a 5-iteration for loop, giving us 6 rows total for the table; a nested 7-iteration for loop writes out the cells of those rows.

// Now create each row. for (j = 0; j < 5; j++) { x = 0; document.writeln("<tr>"); for (i = 0; i < 7; i++) { ... } document.writeln("</tr>"); x += 7; }

Near the end of the inner loop body is a

this.m_myDate.setDate(this.m_myDate.getDate( ) + 1);

line that is key to understanding the script, specifically, this command will walk us through the display month on a day-by-day basis.

Dead cells

For a given display month, 0-6 second row cells will precede the beginning of the month and 0-7 sixth row cells will follow the end of the month: these cells are 'grayed out' by the if clause of an if...else statement that makes up the inner loop body.

for (i = 0; i < 7; i++) { if (this.m_myDate.getDay( ) > (i + x) || this.m_myDate.getMonth( ) != this.month - 1) document.writeln("<td width=\"14%\" bgcolor=\"#" + this.m_strDead + "\">&nbsp;</td>"); else { ... this.m_myDate.setDate(this.m_myDate.getDate( ) + 1); } }

September 1998 was a while ago, wasn't it? Suppose we pass this.month = m = 4 and this.year = y = 2015 to the ampCalendar( ) constructor function to create a calendar for April 2015, whose 1st day falls on a Wednesday and whose 30th day falls on a Thursday.

As detailed two entries ago, this.m_myDate initially points to the first day of the display month. For 1 April 2015, this.m_myDate.getDay( ) returns 3. If the outer loop's j counter is 0 and the inner loop's i counter is 0, 1, or 2, then the this.m_myDate.getDay( ) > (i + x) subcondition returns true and therefore the Sunday, Monday, and Tuesday cells in the second row are given a this.m_strDead (silver) background color but no visible content. (I don't know if removing the &nbsp; non-breaking spaces would cause a validation problem, but there's no harm in keeping them there, of course.)

When i hits 3 - i.e., when we hit 1 April - both if subconditions return false and the second row's Wednesday cell is colored by one of the conditionals described in the following sections. Re the second subcondition, this.m_myDate.getMonth( ) and this.month - 1 are both 3 and will remain so throughout the month of April.

At this point the aforenoted this.m_myDate.setDate(this.m_myDate.getDate( ) + 1); command, which concludes the if...else statement's else clause, begins to push this.m_myDate through the subsequent April days; it increments this.m_myDate.getDate( ) to 2, 3, 4, ... and causes this.m_myDate.getDay( ) to run in a 3-4-5-6-0-1-2 3-4-5-6-0-1-2 ... cycle until we get to the end of the month.

The x = 0, 7, 14, ... variable counts up the number of cells below the header row and - as far as I can tell - the role of the + x operation in the this.m_myDate.getDay( ) > (i + x) subcondition is to prevent i from falling below this.m_myDate.getDay( ) so as to prevent the below-the-second-row cells in the prior-to-the-1st-of-the-month table columns from being grayed out. However, i and this.m_myDate.getDay( ) are in fact equal throughout a given display month: in the same way that a new j row resets i to 0, this.m_myDate.getDay( ) is reset to 0 for the Sunday of that row. Consequently, the x counter is superfluous and can be thrown out.

At the end of the 30 April (j = 4, i = 4) iteration, the setDate( ) operation pushes this.m_myDate to 1 May. At the start of the 1 May (j = 4, i = 5) iteration, the this.m_myDate.getDay( ) > (i + x) subcondition returns false but the this.m_myDate.getMonth( ) != this.month - 1 subcondition returns true - this.m_myDate.getMonth( ) is now 4 for May, whereas this.month - 1 is still 3 - and as a result the Friday cell in the sixth row is grayed out. Although this.m_myDate is not pushed to 2 May for the next (j = 4, i = 6) iteration, the May ≠ April situation still applies and therefore the sixth row's Saturday cell is grayed out as well.

Like the other cells in the table, each m_strDead cell has a width that is 14% of its containing block, whatever that happens to be.

Leading up to today

As intimated above, the this.m_myDate.getDay( ) > (i + x) and this.m_myDate.getMonth( ) != this.month - 1 subconditions are both false and thus the if...else statement's else clause is operative for the dates of the display month. The else clause itself comprises an if...else if...else construct that prints
(a) date numbers
(b) and memos if appropriate
in the display month cells; in addition, it
(i) gives each display month cell a background color that depends on the this.m_myDate and this.m_now dates and months
(ii) and otherwise applies a common set of styles to the cells and their contents.

The construct's if clause

if (this.m_myDate.getDate( ) < this.m_now.getDate( ) && this.m_myDate.getMonth( ) <= this.m_now.getMonth( )) document.writeln("<td valign=\"top\" width=\"14%\" bgcolor=\"#" + this.m_strPast + "\"><b>" + "<font face=\"" + this.font + "\" size=\"" + this.fontSize + "\">" + this.m_myDate.getDate( ) + "</b><br>" + this.getText(this.m_myDate.getDate( )) + "</font></td>");

addresses this.m_myDate date numbers that are less than the this.m_now date number for this.m_myDate month numbers that are less than or equal to the this.m_now month number, regardless of year. For example, if today is 26 March 2015, then the if condition will return true for 1 January through 25 January, 1 February through 25 February, and 1 March through 25 March in 2014 or 2015 or 2016 or any other year. The cells for these dates are marked with a this.m_strPast (#e1e1d1) background color.

Content-wise, a this.m_myDate.getDate( ) date number and perhaps also a this.getText(this.m_myDate.getDate( )) memo are loaded into each this.m_strPast cell. The this.getText(this.m_myDate.getDate( )) command calls the ampcal.js script's ampCalendar_getText( ) function

function ampCalendar_getText(n) { var x = 0; while (x < this.m_rgDay.length) { if (n == this.m_rgDay[x]) return this.m_rgTxt[x]; x++; } return ""; }

and passes thereto this.m_myDate.getDate( ), which is given an n identifier. The ampCalendar_getText( ) function steps through the m_rgDay[ ] array in search of the n date number via an incrementing x index. If a this.m_rgDay[x] value that matches n is found, then the corresponding this.m_rgTxt[x] string in the m_rgTxt[ ] array is returned to the this.getText(this.m_myDate.getDate( )) call in the document.writeln( ) command; if n isn't present in the m_rgDay[ ] array, then an empty string is returned instead. For example, if n = 7, then n will match this.m_rgDay[3] and therefore the this.m_rgTxt[3] = "Take puppies to the vet" string is written to the cell.

Style-wise (over and above the cells' #e1e1d1 bgcolor and 14% width), the date number + memo content is pushed to the top of the cells via a valign='top' attribute and rendered according to the this.font = "Verdana, Arial, Helvetica" font family and this.fontSize = "2" font size; on my computer, a size='2' font element attribute maps onto a font-size:small; style declaration. Also, the date number but not the memo is bolded with a b element.

If we were to tag the this.m_strPast cells via a class='preDates' identifier, then we can recast the if clause as:

.preDates { vertical-align: top; width: 14%; background-color: #e1e1d1; font-family: Verdana, Arial, Helvetica; font-size: small; }
.preDates span { font-weight: bold; }

if (this.m_myDate.getDate( ) < this.m_now.getDate( ) && this.m_myDate.getMonth( ) <= this.m_now.getMonth( )) document.write("<td class='preDates'><span>" + this.m_myDate.getDate( ) + "<\/span><br>" + this.getText(this.m_myDate.getDate( )) + "<\/td>");

We'll deal with the remaining types of data cells in the following entry.

Saturday, March 21, 2015
Calendar Initiation II
Blog Entry #346

Welcome back to our deconstruction of the Java Goodies Calendar and Datebook script. We are at present going through the example.html document's second script element, to which I will give a script1 id for the discussion below. (I was going to call it the scripts[1] script, as though we had gotten it with a var scripts = document.getElementsByTagName("script"); command. However, I see that HTML5, which is now a W3C Recommendation, green-lights the use of the id attribute with the script element; HTML 4 forbade that.)

With the ampCal ampCalendar object and its various properties in hand - see the Initiation I section of the previous post - we next send data for 4 date memos

// Set the text of days.
ampCal.setItem(3, "Dentist Appointment");
ampCal.setItem(7, "Take puppies to the vet");
ampCal.setItem(14, "Call Kathy regarding auto loan");
// Note: you can include HTML tags in the text...
ampCal.setItem(25, "Office party<br><br>Bring pizza");

to the ampcal.js script's ampCalendar_setItem( ) function.

function ampCalendar_setItem(nDay, strText) { x = this.m_rgDay.length; this.m_rgDay[x + 1] = nDay; this.m_rgTxt[x + 1] = strText; }

The ampCalendar_setItem( ) function parks the data's arguments[0] (nDay) values in the m_rgDay[ ] array and its arguments[1] (strText) values in the m_rgTxt[ ] array. At the end of this process, we have:

ampCal.m_rgDay = [undefined, 3, undefined, 7, undefined, 14, undefined, 25];
ampCal.m_rgTxt = [undefined, "Dentist Appointment", undefined, "Take puppies to the vet", undefined, "Call Kathy regarding auto loan", undefined, "Office party<br><br>Bring pizza"];

• The +1 operations in the function are to blame for all those undefineds; these additions are unnecessary because each assignment to the m_rgDay[ ] array itself increases this.m_rgDay.length (x) by one.

• As shown above, the strText memo text can be marked up or otherwise supplemented with HTML. N.B. Andrew's demo prepends an <img src='images/pizza.jpg'> image to the last setItem memo.

• You may be wondering, "Why are we storing this stuff at all? Can't we directly load the memos into the calTable table cells?" We can in fact do that but not before we've built the table, so just hold tight, OK?

Subsequently the script1 script jazzes up the title string a bit

// Set the title to something cool.
ampCal.title = ampCal.title + " - Eat at Joe's";

and then unnecessarily resets the fontSize value to 2

ampCal.fontSize = "2";
// The ampCalendar( ) constructor function previously set this.fontSize to 2.

and then bumps up the headSize value to 3.

ampCal.headSize = "3"; // This is the Sunday, Monday, ... line.

(If these seem like odd font sizes to you, that's because they're actually values for the size attribute of the font element - we'll CSS-ize them in due course.)

The script1 script concludes with a call to the ampcal.js script's ampCalendar_Display( ) function:

// Now show off our calendar.
ampCal.display( );

The ampCalendar_Display( ) function builds the calendar from the ground up and writes it to the page, and also places a brief credit below the calendar - we probably won't get through all of it in this post, but let's get started, shall we? The first ampCalendar_Display( ) command outputs the title string as a horizontally centered h1 heading:

function ampCalendar_Display( ) { document.writeln("<h1><center>" + this.title + "</center></h1>");

The h1-h6 heading elements all have an (%inline;)* content model, which would be violated by a center element child if we were writing this out as normal HTML. Of course, we should really carry out the <center>ing with an h1 { text-align:center; } style rule.

• The document object's write( ) and writeln( ) methods give identical renderings in almost all cases, and you may use the write( ) method in place of the writeln( ) method throughout the ampCalendar_Display( ) function.

• Although old-school, the use of the writeln( ) method to write out the calendar does require fewer lines of code than would a modern-day DOM approach, i.e., creating the elements with the createElement( ) method, deploying the elements with the appendChild( ) method, etc.

• As ampcal.js is an external script, I don't know if its unescaped HTML end-tags (</center>, </h1>, ...) would pose any problems if we were to validate the example.html document: you may want to escape those guys to be on the safe side.

As the title heading heads the calTable table, we could alternatively captionize it

caption { font-weight: bold; font-size: 32px; } /* This'll give it an <h1>-ish appearance. */

document.write("<caption>" + this.title + "</caption>");

just inside the table element start-tag, which brings us to the next ampCalendar_Display( ) command:

document.writeln("<table border=\"1\" cellpadding=\"3\" bordercolor=\"#FFFFFF\" bordercolordark=\"#FFFFFF\" bordercolorlight=\"#FFFFFF\">");

The border, bordercolor, bordercolordark, and bordercolorlight attributes can and should be replaced by a table, td { border:1px solid white; } style rule. The bordercolordark and bordercolorlight attributes are only supported by IE - check out the picture at Dottoro's borderColorDark attribute page if you're curious about what they do - moreover, as they are set to the same color, they don't do anything that the bordercolor attribute doesn't do and are therefore superfluous. For its part, the bordercolor attribute has cross-browser support (contra Dottoro, Opera does support it) but it's not standard so you shouldn't be using it, either.

Moving on, a set of 9 commands writes out the first table row:

document.writeln("<td width=\"14%\" bgcolor=\"#000080\"><font color=\"#FFFFFF\" face=\"" + this.font + "\" size=\"" + this.headSize + "\"><strong>Sunday</strong></font></td>");
/* ...Corresponding commands for Monday thru Saturday... */

The Sunday-to-Saturday cells are header cells and should be marked up as th elements. As td content, the day names are left-justified and have a normal font weight; as th content, they are horizontally centered and bolded.

A 14% th | td width means that the cell width will be 14% of the viewport width (if we were to give the calTable table a specific width - say, width:75%; - then the cell width would be 14% of that width). The #000080 color has a "navy" name. A size="3" font element attribute maps onto a (default-value) font-size:medium; style declaration on my computer.

In moving the first row's presentational features to a style sheet, here's the rule I'd use:

th { width: 14%; background-color: navy; color: white; font-family: Verdana, Arial, Helvetica; }

• The width and bgcolor attributes of the th | td element, the font element itself, and the color and face and size attributes of the font element are all deprecated.
• The strong element is still legit but we don't need it for th cells, which do their own bolding as noted above.
• You can left-justify the <th> day names with a text-align:left; styling but I actually like 'em horizontally centered.

Moreover, getting the presentational stuff out of the way makes it a lot easier to write the headers iteratively if you'd like to do that:

var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
for (var i = 0; i < dayNames.length; i++) document.write("<th>" + dayNames[i] + "</th>");

Let's save the data part of the calTable table for the following entry.

Thursday, March 12, 2015
Let's Make an Appointment
Blog Entry #345

I've been slogging through the Calendars, Clocks, and Calculators sector of the Java Goodies JavaScript Repository over the past few days, cataloguing what's there, and I'm sorry to report that its pickings are a lot slimmer than I originally thought they would be. We won't be spending nearly as much time in this sector as we did in the Scripts that Display Text sector.

The Quadratic Equation script is followed by a Timezone Buttons script that codes a World Clock. The Timezone Buttons demo works just fine although its color="#FFFFFF" text input labels are invisible due to the white background; the Grab the Script page is gone but you can still get the script at the JavaScript Goodies site. The Timezone Buttons script is evidently a slightly earlier version of the JavaScript Script Tips #87-90 script that we dissected in January-March 2008. (At least now I know who wrote the script. Cheers, Randy.)

The Timezone Buttons script is followed by a Calendar and Datebook script for which Joe does not provide a demo but whose package is still available. The Calendar and Datebook script codes a calendar for a month of your choosing - either the current month or a past or future month - and places custom memos - e.g., "Dentist Appointment", "Take puppies to the vet" - on specific dates of the calendar.

We will discuss the Calendar and Datebook script for at least the next two entries.

The ampcal/ package

Authored by Andrew Pierce in 1998, the Calendar and Datebook code comprises an ampcal/ package of three files:
(1) ampcal.js
(2) example.html
(3) readme.txt

The ampcal.js script builds the calendar/datebook ("calendar" hereafter) from scratch, and is imported into the example.html display document via:

<!-- Include the calendar source file -->
<script language="JavaScript" src="ampcal.js"></script>
<script language="JavaScript"> /* ...The internal example.html script... */ </script>

The example.html document sets the calendar's month and year, date memos, heading, and font sizes, and writes the calendar to the page.

The example.html document concludes with the following notice:

<p>Email the author: <a href="">Andrew Pierce</a><br>
<a href="">Visit the ampCal web page.</a></p>

The page is no longer extant: a search for it at the Internet Archive led me to a functioning demo.

The readme.txt file is, well, a readme-type file.

Calendar overview

The calendar is housed in a 6-row*, 7-cells-per-row table, which doesn't have an identifier so let's give it an id="calTable". Could we put the calendar in a div of 42 spans? Sure. But a calendar counts as a grid of data in my book, and therefore I would say that the table structure is appropriate.
(*We'll see later that some months require a 7th row.)

The calTable table's first (rows[0]) row is filled with a Sunday-to-Saturday set of headers. The remaining rows are populated with date numbers and memos as appropriate for the display month. The rows[1]-rows[5] cells are color-coded per criteria we will discuss in due course.

The calendar is completely coded by two scripts: the external ampcal.js script and an internal example.html script. There is no separation at all between structure, presentation, and behavior. The calendar structure can be written out as normal HTML if desired, although doing this will greatly increase the calendar 'footprint' in the example.html document. The calendar stylings are easily moved to an external or internal style sheet.

Initiation I

An ampCal Object object bundles
(a) various data for the calendar and
(b) functionality for storing/retrieving the date memos and for displaying the calendar.

• I'm tempted to say that the ampCal object 'represents' the calendar, but that would not really be true, as the ampCal data doesn't intersect with the calendar structure in any way.
• As the Mozilla JavaScript Guide's discussion of constructor functions does not specify that a constructor function (vide infra) creates an Object object, you'll just have to take my word for it that window.alert(ampCal); displays an [object Object] message.

The ampcal.js script contains 5 functions and no top-level commands. The Calendar and Datebook action effectively begins with the internal example.html script, whose first operation creates the ampCal object by instantiating an ampCalendar object type.

ampCal = new ampCalendar(9, 1998);

The ampCalendar( ) constructor function

function ampCalendar(m, y) { ... }

is the last function in the ampcal.js source; it defines a total of 21 properties for the calendar, 4 of which are methods; its two arguments are stored in m and y variables.

The ampCalendar( ) function first creates empty arrays for storing the date memo data, and assigns them to m_rgDay and m_rgTxt properties.

this.m_rgDay = new Array( );
this.m_rgTxt = new Array( );

Next, m_strDead, m_strFutr, m_strPast, and m_strNow properties are set to #-less RRGGBB colors

this.m_strDead = "C0C0C0";
this.m_strFutr = "F5DEB3";
this.m_strPast = "E1E1D1";
this.m_strNow = "F08080";

with which we'll paint the rows[1]-rows[5] cell backgrounds. FYI: The #C0C0C0 color has a "silver" name, the #F5DEB3 color is "wheat", and the #F08080 color is "lightcoral" - see the Extended color keywords section of the CSS Color Module Level 3 specification. The #E1E1D1 color doesn't have a name as far as I know.

An array of the numbers of days/month for a non-leap year is assigned to an m_rgDays property; an array of month names is assigned to an m_rgMths property.

this.m_rgDays = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
this.m_rgMths = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");

Font family data are loaded into a font property; font size data are loaded into fontSize and headSize properties.

this.font = "Verdana, Arial, Helvetica";
this.fontSize = "2";
this.headSize = "2";

The m parameter value, 9, is assigned to a month property; the y parameter value, 1998, is assigned to a year property.

this.month = m;
this.year = y;

Subsequently, m_now and m_myDate properties are set to Date objects for the current date and for the first date of the display month, respectively.

this.m_now = new Date( );
this.m_myDate = new Date(y, m - 1, 1);

After that, this.m_myDate.getMonth( ), 8, is plugged into the this.m_rgMths[ ] array to give September, which is assigned to a monthName property. The this.monthName value, a space character, and the this.year value are concatenated to give a September 1998 string, which is assigned to a title property.

this.monthName = this.m_rgMths[this.m_myDate.getMonth( )];
this.title = this.monthName + " " + this.year;

We're getting there. References for ampCalendar_setFebDays( ), ampCalendar_Display( ), ampCalendar_setItem( ), and ampCalendar_getText( ) functions are then assigned respectively to setFebDays, display, setItem, and getText properties: we'll discuss these functions when we get to them.

this.setFebDays = ampCalendar_setFebDays;
this.display = ampCalendar_Display;
this.setItem = ampCalendar_setItem;
this.getText = ampCalendar_getText;

The ampCalendar( ) function concludes with a call to the ampCalendar_setFebDays( ) function.

this.setFebDays( ); } // That's it for ampCalendar( ).

Look before you leap

If this.month is 1 and this.year is a leap year, then the ampCalendar_setFebDays( ) function, which is the first function in the ampcal.js source, adjusts this.m_rgDays[1] to 29.

function ampCalendar_setFebDays( ) { if (this.month == 1) { if (this.year % 4 == 0) { if (this.year % 100 == 0) { if (this.year % 400 == 0) this.m_rgDays[1] = 29; } else this.m_rgDays[1] = 29; } } }

• The this.month == 1 test actually flags January and not February: recall that this.month is one greater than this.m_myDate.getMonth( ).

• As I trust you know, there is a bit more to the leap year thing than just divisibility by 4, namely, years that are divisible by 100 but not by 400 (e.g., 1900, 2100) are not leap years.

• The this.year conditional code is more verbose than it needs to be, and can be recast as:

if (!(this.year % 4) && (this.year % 100) || !(this.year % 400)) this.m_rgDays[1] = 29;

Having said all this, the ampCalendar_setFebDays( ) functionality and the m_rgDays[ ] array are actually excess baggage, and can be thrown out: as we'll see in the next post, the ampCalendar_Display( ) function calls on several Date object methods to build the calendar and doesn't make use of the m_rgDays[ ] array at all.

We'll continue our Calendar and Datebook deconstruction in the following entry.

Powered by Blogger

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