reptile7's JavaScript blog
Monday, January 21, 2008
 
The Ticking Image
Blog Entry #101

Back in Blog Entry #61, we discussed HTML Goodies' JavaScript Script Tips #25-28, whose script codes a basic digital clock:

Script Tips #84, #85, and #86 offer a script that codes a similar but fancier image-based digital clock:
 

(Through a combination of incorrect image URL paths and wrongly escaped [ and ] characters, the demos at the 'current' Script Tips #84-86 pages do not work. However, the demos at the 'legacy' Script Tips #84-86 pages (e.g., here) work fine.)

In today's entry we'll dissect the Script Tips #84-86 Script, which can be accessed by following the Here's the Code links in all three script tips and is reproduced in the div below:

<html>
<head>
<script type="text/javascript">
<!--

var d = new Array( );
for (i = 0; i < 10; i++) {
d[i] = new Image( );
d[i].src = "dgt" + i + ".gif";
}
var pm = new Image( );
pm.src = "dgtp.gif";
var am = new Image( );
am.src = "dgta.gif";
var dates, min, sec, hour;
var amPM = "am";

function clock( ) {
dates = new Date( );
hour = dates.getHours( );
min = dates.getMinutes( );
sec = dates.getSeconds( );
if (hour < 12) {
amPM = am.src;
}
if (hour > 11) {
amPM = pm.src;
hour = hour - 12;
}
if (hour == 0) {
hour = 12;
}
if (hour < 10) {
document["tensHour"].src = "dgtbl.gif";
document["Hour"].src = d[hour].src;
}
if (hour > 9) {
document["tensHour"].src = d[1].src;
document["Hour"].src = d[hour - 10].src;
}
if (min < 10) {
document["tensMin"].src = d[0].src;
}
if (min > 9) {
document["tensMin"].src = d[parseInt(min / 10, 10)].src;
}
document["Min"].src = d[min % 10].src;
if (sec < 10) {
document["tensSec"].src = d[0].src;
}
if (sec > 9) {
document["tensSec"].src = d[parseInt(sec / 10, 10)].src;
}
document["Sec"].src = d[sec % 10].src;
document["amPM"].src = amPM;
setTimeout("clock( );", 100);
}
//-->
</script>
</head>
<body bgcolor="#ffffff" onload="clock( );">
<center>

<img src="dgtbl.gif" name="tensHour" /><img src="dgtbl.gif" name="Hour" /><img src="dgtcol.gif" /><img src="dgtbl.gif" name="tensMin" /><img src="dgtbl.gif" name="Min" /><img src="dgtcol.gif" /><img src="dgtbl.gif" name="tensSec" /><img src="dgtbl.gif" name="Sec" /><img src="dgtbl.gif" /><img src="dgtbl.gif" name="amPM" />

</center>

</body></html>

The clock images

The Script Tips #84-86 Script's clock makes use of fourteen images for its display:
(1-10) Ten dgt#.gif images of the 0-9 digits for the hour:minute:second parts of the clock;
(11-12) dgta.gif and dgtp.gif images of the letters A and P for AM and PM, respectively;
(13) A dgtcol.gif image of a colon for delimiting the hour:minute:second parts of the clock; and
(14) A default, 'blank' dgtbl.gif image.
These images are collected at the beginning of Script Tip #84 and can be downloaded in the usual manner (on each image, right-click and then on the contextual menu go to Download Image to Disk or whatever on your computer). Dimensions-wise, all of these images are 16px by 21px except for the dgtcol.gif image, which is 7px by 21px.

In the script document body, the display itself comprises a 'string' of ten back-to-back img elements. In source order, and as shown above:
(1-2) The first (name="tensHour") img element displays the 'tens place' of the hour part of the clock; the second (name="Hour") img element displays the 'ones place' of the hour part of the clock.
(3) The third img element uniformly displays the clock's hour:minute colon.
(4-5) The fourth (name="tensMin") and fifth (name="Min") img elements respectively display the tens and ones places of the minute part of the clock.
(6) The sixth img element uniformly displays the clock's minute:second colon.
(7-8) The seventh (name="tensSec") and eighth (name="Sec") img elements respectively display the tens and ones places of the second part of the clock.
(9) The ninth img element uniformly displays the space between the second and AM/PM parts of the clock.
(10) The tenth (name="amPM") img element displays the AM/PM part of the clock.

Validation notes: XHTML 1.0 deprecates the name attribute of the img element. In addition, an appropriate alt attribute should be added to each img element; the img alt attribute has a #REQUIRED designation even in the HTML 4.01 Transitional DTD.

Like the Script Tips #25-28 clock, the Script Tips #84-86 clock displays a 1-to-12 hour value, a 2-digit 00-to-59 minute value, and a 2-digit 00-to-59 second value. In building the Script Tips #25-28 clock, we showed in Blog Entry #61 that we could use the normal returns of the getHours( ), getMinutes( ), and getSeconds( ) methods of the core JavaScript Date object 'as is' most of the time. In contrast, the Script Tips #84-86 clock display has at its disposal a much smaller set of 'values' (images) and thus must deal separately with the tens and ones places of its hour:minute:second parts as intimated above. (Yes, you could create a set of sixty images corresponding to the getHours( )/getMinutes( )/getSeconds( ) returns, but I for my part would rather not do that.) As a result, the Script Tips #84-86 Script is somewhat more complicated than is the Script Tips #25-28 Script, but it's nothing we can't handle and maybe improve on a bit...

Deconstruction of the Script Tips #84-86 Script

Before the script document body is rendered

<script type="text/javascript">

var d = new Array( );
for (i = 0; i < 10; i++) {
d[i] = new Image( );
d[i].src = "dgt" + i + ".gif"; }

Validation note: Scripts containing one or more < characters should be externalized for XHTML-compliance.

We initially preload the dgt#.gif images, automating the process via a for loop. We first preloaded images in HTML Goodies' JavaScript Primers #27; more relevantly, we previously used a for loop to preload images automatedly in the "Other code possibilities" section of Blog Entry #64. The dgt#.gif image URLs are assigned to a d array.

var pm = new Image( ); pm.src = "dgtp.gif";
var am = new Image( ); am.src = "dgta.gif";

These lines similarly preload the dgtp.gif and dgta.gif images, whose URLs are respectively assigned to pm.src and am.src.

Two more points before moving on:
• Joe evidently didn't find it necessary to preload the dgtcol.gif and dgtbl.gif images, although you could certainly preload them if you wanted to:

var colon = new Image( ); colon.src = "dgtcol.gif";
var blank = new Image( ); blank.src = "dgtbl.gif";

• I can confirm that (at least on my computer) the Script Tips #84-86 clock doesn't run smoothly if the image preloading code is commented out.

var dates, min, sec, hour, amPM = "am";

The variables dates, min, sec, and hour are declared but not initialized. Also, the variable amPM (not the best choice for a variable name given that the document's last img element is named amPM) is declared and given an am string value.

Loading the document body HTML

All of the document's img elements are initially loaded with the dgtbl.gif image except for the third- and sixth-in-source-order img elements, which are loaded with the dgtcol.gif image.

For validating the script document against the XHTML 1.0 Strict DTD, wrap the images in a <div id="imageString"> ... </div> container and replace the presentational markup (the body element's bgcolor attribute and the center element) with the following style rules:

body { background-color: white; }
#imageString { margin-left: auto; margin-right: auto; width: 144px; }

When the script document has loaded, the body element's onload="clock( );" attribute triggers the clock( ) function in the script's script element.

The clock( ) function

We begin by grabbing the current time:

function clock( ) {
dates = new Date( );
hour = dates.getHours( );
min = dates.getMinutes( );
sec = dates.getSeconds( );

This is code that we know like the backs of our hands at this point; Mozilla discusses the Date object on this page and the above Date object methods on this page if you could use refreshers for these guys.

if (hour < 12) amPM = am.src;
if (hour > 11) {
amPM = pm.src;
hour = hour - 12; }
if (hour == 0) hour = 12;
if (hour < 10) {
document["tensHour"].src = "dgtbl.gif";
document["Hour"].src = d[hour].src; }
if (hour > 9) {
document["tensHour"].src = d[1].src;
document["Hour"].src = d[hour - 10].src; }

A series of five if conditionals sets the hour part of the clock and lays some groundwork for setting later the AM/PM part of the clock; for now, let's focus on the clock's hour part.

The second if conditional adjusts the 12-23 hour (dates.getHours( )) values to a 0-11 range.
The third if conditional adjusts the 0 hour values (for the midnight-to-1AM and noon-to-1PM hours) to 12.
It occurred to me that the abs( ) method of the core JavaScript Math object would allow us to carry out these adjustments via one conditional:

if (hour > 12 || hour == 0) hour = Math.abs(hour - 12);

With a 1-12 hour range now in hand, we're ready to load some images into the tensHour and Hour img element placeholders for the hour part of the clock.
For the 1-9 hour values, the fourth if conditional loads the dgtbl.gif image into the tensHour placeholder and the d[hour].src (dgt+hour+.gif) image into the Hour placeholder.
Because we don't have images of the numerals 10, 11, and 12, the 10-12 hour values must for display be split into separate digits; accordingly for these values, the fifth if conditional loads the d[1].src (dgt1.gif) image into the tensHour placeholder and the d[hour-10].src (dgt0.gif, dgt1.gif, or dgt2.gif) image into the Hour placeholder.

You are probably wondering, "That document["imageName"] syntax for referencing an image...that's not standard, is it?" Indeed, it is not - before the fact, you would expect it to throw errors, but it doesn't (at least on my computer) - document.imageName or document.images["imageName"] references would be legit, but given that XHTML deprecates the img name attribute (vide supra), I encourage you to instead use ordinal document.images[#] references per my alternate code samples below.

JavaScript's* ternary ?: conditional operator allows a more concise formulation of the fourth and fifth if conditionals:

document.images[0].src = (hour < 10) ? "dgtbl.gif" : d[1].src;
document.images[1].src = (hour < 10) ? d[hour].src : d[hour - 10].src;

The ?: operator is somewhat like a simple if...else statement. The ?: syntax is:

condition ? expression1 : expression2

If the condition is true, then expression1 is evaluated; if it's false, then expression2 is evaluated. In my ?: code above, different image URLs are assigned to the src properties of the zeroth (tensHour) and first (Hour) img placeholders depending on whether hour is or is not less than 10. The parentheses surrounding the hour<10 condition(s) are actually unnecessary (they make the code a bit more readable, IMO), because the < comparison operator has a higher priority than does the ?: operator.

(*FYI: The ?: operator is not specific to JavaScript but, according to its Wikipedia entry, is part of at least twelve other programming languages.)

Let's move on to the minute part of the clock. The 0-59 min (dates.getMinutes( )) return is converted to a 00-59 display via two if conditionals that set the tens place and a subsequent statement that sets the ones place:

if (min < 10) document["tensMin"].src = d[0].src;
if (min > 9) document["tensMin"].src = d[parseInt(min / 10, 10)].src;
document["Min"].src = d[min % 10].src;

For the 0-9 min values, the first if conditional loads the d[0].src (dgt0.gif) image into the tensMin img placeholder.
The 10-59 min values must for display be split into separate digits; for these values, the second if conditional extracts the min tens-place digit via
(a) dividing min by 10 and then
(b) lopping off the .# part of the quotient with the top-level parseInt( ) function**
within the square brackets of a d[ ].src expression. The resulting d[#].src URL is assigned to document["tensMin"].src, loading the dgt1.gif, dgt2.gif, dgt3.gif, dgt4.gif, or dgt5.gif image into the tensMin placeholder. For example, if min is 49, then 49/10 gives 4.9 and parseInt(4.9) gives 4, and consequently the tensMin placeholder is loaded with the dgt4.gif image.
(**The radix=10 second parseInt( ) parameter is unnecessary and can be removed.)

I can think of two other ways to extract the min tens-place digit.
(1) Divide min by 10 and then round down via the floor( ) method of the Math object;

if (min > 9) document.images[3].src = d[Math.floor(min / 10)].src;

(2) Stringify min and then use the charAt( ) method of the core JavaScript String object as follows:

var minstring = min.toString( );
/* Mozilla discusses the toString( ) method of the core JavaScript Number object here. */
if (min > 9) document.images[3].src = d[minstring.charAt(0)].src;
/* String-to-number conversion occurs automatically in the right side of the preceding statement. */

As for the Min img element, a min%10 operation yields the correct ones-place digit for all min values. If min is 49 per the above example, then 49%10 gives 9 and consequently the Min placeholder is loaded with the d[9].src (dgt9.gif) image. (We similarly could have used an hour%10 operation in filling the Hour placeholder.) Alternatively, if min has been stringified, then we can use the charAt( ) method to extract the ones-place digit:

document.images[4].src = d[minstring.charAt(1)].src;

Other than its different identifiers, the code for setting the second part of the clock is identical to that for the clock's minute part:

if (sec < 10) document["tensSec"].src = d[0].src;
if (sec > 9) document["tensSec"].src = d[parseInt(sec / 10, 10)].src;
document["Sec"].src = d[sec % 10].src;

The clock( ) function's minute and second blocks can also be condensed via the ?: operator:

document.images[3].src = (min > 9) ? d[parseInt(min / 10)].src : d[0].src;
document.images[4].src = d[min % 10].src;
document.images[6].src = (sec > 9) ? d[parseInt(sec / 10)].src : d[0].src;
document.images[7].src = d[sec % 10].src;

We next turn to the AM/PM part of the clock, which is set with:

document["amPM"].src = amPM;

This statement loads the amPM image - dgta.gif or dgtp.gif, depending on the time of day - into the amPM img placeholder; in the clock( ) function's hour block, the am.src (dgta.gif) URL was assigned to amPM if hour<12 and the pm.src (dgtp.gif) URL was assigned to amPM if hour>11. More efficiently, we can use the ?: operator to ditch the amPM variable and put all of the AM/PM code on one command line:

document.images[9].src = (hour < 12) ? am.src : pm.src;

Finally, a setTimeout( ) command recursively calls the clock( ) function after a 100-millisecond delay, updating the clock display:

window.setTimeout("clock( );", 100);

In the next post, we'll begin a look at a more ambitious digital clock script - one that takes time zones and daylight-saving time into account - covered by Script Tips #87-90.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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