reptile7's JavaScript blog
Tuesday, January 23, 2007
 
Bannerama!
Blog Entry #64

Today we'll discuss HTML Goodies' JavaScript Script Tip #34 and its random banner script; actually, "discuss" doesn't quite do the situation justice - "rescue from oblivion" is more like it.

Script Tip #34's "Take a look at the script" link leads to a blank page. Well, OK, it's not "blank" - it contains the HTML Goodies site template, and there are some ads on the right-hand side - but the script isn't there. Bizarrely, the second half of the page's source, where the <!-- New content here --> would be, is simply missing. Moreover, the Script Tip #34 Script makes use of five images, which are also AWOL.

However, a detailed knowledge of the HTML Goodies site pays dividends here. In Blog Entry #57, we fished Iceman's browser sniffer out of HTML Goodies' /legacy/beyond/javascript/stips/ subdirectory. Gratifyingly, the scripttip34script.html page of this subdirectory contains the Script Tip #34 Script, which is reproduced below:

<script language="javascript">

banners = new Array( );
banners[0] = "<img border='0' src='banner0.gif'>";
banners[1] = "<img border='0' src='banner1.gif'>";
banners[2] = "<img border='0' src='banner2.gif'>";
banners[3] = "<img border='0' src='banner3.gif'>";
banners[4] = "<img border='0' src='banner4.gif'>";

GoTo = new Array( );
GoTo[0] = "http://www.htmlgoodies.com";
GoTo[1] = "http://www.developer.com";
GoTo[2] = "http://www.mtv.com";
GoTo[3] = "http://www.vh1.com";
GoTo[4] = "http://www.whitehouse.gov";

var Number = Math.round(4 * Math.random( ));

var TheLink = GoTo[Number];
var TheImage = banners[Number];

document.write("<center><a href=" + TheLink + ">" + TheImage + "</a></center>");

</script>

(We will in future entries encounter several other Script Tip scripts whose page URLs are botched in the same way.)

And what about those images, huh? They, too, can be found in the /legacy/beyond/javascript/stips/ subdirectory.

The banner0.gif image, HTML Goodies banner, is here.

The banner1.gif image, Developer.com banner, is here.

The banner2.gif image, MTV.com banner, is here.

The banner3.gif image, VH1.com banner, is here.

The banner4.gif image, White House banner, is here.

Joe's Script Tip #34 Script demo does not work and would not work even if the banner#.gif images were on hand because, once again, the script's [ and ] square bracket characters have fatally been escaped to &#91; and &#93;, respectively, as they were in the Script Tips #31-33 Script. But check out this "legacy" Script Tip #34 page, whose source is OK and whose demo does indeed function.

I offer a modified demo in the div below:




Overview of the Script Tip #34 Script As shown above, the Script Tip #34 Script displays a random, rectangular banner - a .gif image that hyperlinks to the Web site indicated by the text on the image. Joe doesn't say how he created the banner#.gif images, but on the basis of HTML Goodies' Ad Banners Primers, my guess is that he used the PaintShop Pro graphics editor. We'll see later in the post that we can make almost identical banners via a combination of HTML and CSS. Joe concedes that the Script Tip #34 Script is "all review," but a bit of deconstruction won't kill us. The script begins by creating an array of 'latent' images: five img elements housing the banner#.gif images are stringified and assigned as elements to the array variable banners. Why use image strings? Because we'll be displaying the banner#.gif images by plugging the banners elements into a document.write( ) command at the end of the script, and besides, we can't directly variabilize the img elements themselves: banners[0] = <img border='0' src='banner0.gif'>; promptly throws a syntax error. However, the banner#.gif images can be directly arrayed as document objects, as we'll see in the "Other code possibilities" section below. Secondly, the script creates a parallel GoTo array of five URL strings that respectively specify 'destination anchors' for the banners[i] images. We next generate a random integer, Number, that ranges from 0 to 4, inclusive, via the random( ) and round( ) methods of the core JavaScript Math object - we recently discussed both of these methods in Blog Entry #58: var Number = Math.round(4 * Math.random( )); (Alternatively, Number can also be generated, if not quite as randomly, by the methodology of HTML Goodies' JavaScript Primers #20: var Number = new Date( ).getSeconds( ) % 5;.) Number then serves as a common index number conjugating the banners and GoTo arrays. banners[Number] will be our random banner, and GoTo[Number] is where we'll go when we click on that banner. All that remains is to get it all to the page with a document.write( ) command: var TheLink = GoTo[Number]; var TheImage = banners[Number]; document.write("<center><a href=" + TheLink + ">" + TheImage + "</a></center>"); Linkwise, the banners[Number] (TheImage) image is the content of an anchor element whose href attribute value is GoTo[Number] (TheLink). Other code possibilities Let's start by separating out the presentational aspects of the script: <style type="text/css"> a { display: block; text-align: center; } img { border-width: 0; } /*The CSS Level 1 Specification notes, After a '0' [length value] number, the unit identifier is optional.*/ </style> Next, let's organize the banner#.gif images as an object array; a for loop is useful in this regard (and especially so if you wanted to use the script with a larger number of banners): var banners = new Array( ); for (i=0; i<=4; i++) { banners[i] = new Image(300,50); banners[i].src = "banner" + i + ".gif"; } // This caches the banners for faster loading. Now, let's get the rest of the HTML out of the script; put the following code after the style block and before the script element start-tag: <body> <a id="randomLink"><img id="randomImage"></a> Finally, remove the script's last three commands (the TheLink declaration, the TheImage declaration, and the document.write( ) command) and instead display the random, linking banner with: document.getElementById("randomLink").href = GoTo[Number]; document.getElementById("randomImage").src = banners[Number].src; There. We're all up-to-date. Let's make our own banners It's not necessary to horse around with a program like PaintShop Pro or Photoshop to create the simple banners of the Script Tip #34 Script; HTML and CSS alone are up to the task. Behold the banner below: Blogger.com Here's the HTML I used: <a href="http://www.blogger.com/"><span>Blogger.com</span></a> And here's my CSS: a { display: block; width: 300px; height: 50px; margin-left: auto; margin-right: auto; background-color: red; text-align: center; text-decoration: none; } span { position: relative; top: 13px; font-size: 24px; font-family: sans-serif; color: white; } The banner is horizontally centered on the page by the anchor element's margin-left: auto; margin-right: auto; style declarations. In turn, the "Blogger.com" text is (approximately) vertically centered in the banner by the span element's position: relative; top: 13px; style declarations.

We'll move on to Script Tips #35-37 in the next post and dissect a script that displays a text message as a cyclical scroll.

reptile7

Labels:


Sunday, January 14, 2007
 
Save the Date
Blog Entry #63

June 2016 Update:
(1) The demo in the legacy Script Tips #31-33 works just fine, so it is almost certainly not Joe but one of his successors at HTML Goodies who is to blame for the dysfunctional demo in the current Script Tips #31-33.
(2) Contra Netscape, the toLocaleDateString( ) method of the Date object does not go back to JavaScript 1.0 but was implemented in JavaScript 1.5 - it's not present in JavaScript 1.4 - so yes, we should cut Joe some slack for not using it.

As we did a couple of entries ago, let's go back to HTML Goodies' JavaScript Primers #3 and this time look at the date-formatting parts/effects of its scripts.
(1) The Primer #3 Script writes out: Today's date is 1-1-2007.
(2) Joe's Primer #3 Assignment answer writes out: You stopped by on: 1/1/2007.
"These date formats are so cold, so antiseptic," you are thinking to yourself. "It'd be way nicer if we could write out: Today is Monday January 1st, 2007." And you're in luck - Joe himself has crafted a script that creates and displays a text-based date in this very format, and he discusses it in HTML Goodies' JavaScript Script Tips #31, #32, and #33.

Joe posts the Script Tips #31-33 Script here but his demonstration thereof in Script Tips #31-33 does not work. Checking the Script Tips #31-33 source code shows that the script's [ and ] square bracket characters have been escaped with the &#91; and &#93; numeric character references, respectively, killing the script's effect. (91 and 93 are the respective ASCII code positions of the [ and ] characters.) Arrrgh!! Joe, you really should know better than this!

Anyway, the div below provides a 'real-date' demo of the Script Tips #31-33 Script followed by the script itself:


<script language="javascript">

DaysofWeek = new Array( );
DaysofWeek[0] = "Sunday";
DaysofWeek[1] = "Monday";
DaysofWeek[2] = "Tuesday";
DaysofWeek[3] = "Wednesday";
DaysofWeek[4] = "Thursday";
DaysofWeek[5] = "Friday";
DaysofWeek[6] = "Saturday";

Months = new Array( );
Months[0] = "January";
Months[1] = "February";
Months[2] = "March";
Months[3] = "April";
Months[4] = "May";
Months[5] = "June";
Months[6] = "July";
Months[7] = "August";
Months[8] = "September";
Months[9] = "October";
Months[10] = "November";
Months[11] = "December";

RightNow = new Date( );

var day = DaysofWeek[RightNow.getDay( )];
var date = RightNow.getDate( );
var Month = Months[RightNow.getMonth( )];
var Year = RightNow.getFullYear( );

if (date == 1 || date == 21 || date == 31)
{ ender = "<sup>st</sup>"; }

else
if (date == 2 || date == 22)
{ ender = "<sup>nd</sup>"; }

else
if (date == 3 || date == 23)
{ ender = "<sup>rd</sup>"; }

else
{ ender = "<sup>th</sup>"; }

document.write("Today is " + day + " " + Month + " " + date + ender + ", " + Year + ".");
</script>

Overview of the Script Tips #31-33 Script

As shown by the document.write( ) command at the end of the script, the outputted date string has the following 'anatomy':

"Today is " + day + " " + Month + " " + date + ender + ", " + Year + "."

day is the textual day of the week: Monday, Tuesday, etc.
Month is the textual month of the year: January, February, etc.
date is the unaltered return for the getDate( ) method of the Date object, which gives the numerical date of the month and runs normally from 1 to 31, 30, 28, or 29, as appropriate
ender is a superscripted ordinal number suffix for date: st, nd, rd, or th
Year is the unaltered return for the getFullYear( ) method of the Date object, which gives a four-digit year

day

Joe stringifies the textual days of the week and organizes them as an array, DaysofWeek. In order, DaysofWeek's elements run from Sunday (DaysofWeek[0]) to Saturday (DaysofWeek[6]) so that the DaysofWeek index numbers and values match the corresponding returns of the getDay( ) method of the Date object, which run from 0 for Sunday to 6 for Saturday - in Joe's words, [W]e are going to play into JavaScript's indexing system.

We can then use the getDay( ) return as a DaysofWeek index number to obtain day as the textual day of the week:

RightNow = new Date( );
var day = DaysofWeek[RightNow.getDay( )];

Suppose it's Monday, for which RightNow.getDay( ) returns 1. Plugging RightNow.getDay( ) into the square brackets of DaysofWeek[ ] thus gives DaysofWeek[1], whose value is Monday, which is assigned to day. Very logical.

Month

As for day, so for Month. Joe stringifies the textual months of the year and organizes them as an array, Months. In order, Months's elements run from January to December so that the Months index numbers and values match the corresponding returns of the getMonth( ) method of the Date object. We then similarly use the getMonth( ) return as a Months index number to obtain Month as the textual month of the year:

var Month = Months[RightNow.getMonth( )];

Suppose it's June, for which RightNow.getMonth( ) returns 5. The Months[RightNow.getMonth( )] expression becomes in effect Months[5], whose value is June, which is assigned to Month.

ender

A series of four if...else statements looks at date, the RightNow.getDate( ) return, and creates an appropriate superscripted suffix therefor. The conditions of the first three if branches all make use of the == comparison operator and the || logical operator - operators that we know like the backs of our hands at this point. For example, here's the first if conditional:

if (date == 1 || date == 21 || date == 31)
{ ender = "<sup>st</sup>"; }

The code above says, "If it's true that date is equal to 1 OR 21 OR 31, then superscript st and assign the result to ender." The HTML sup element is discussed here in the HTML 4.01 Specification.

Analogously, the second if conditional assigns nd to ender if date is 2 or 22 and the third if conditional assigns rd to ender if date is 3 or 23. The final else statement creates a default suffix th for all other values of date.

Other code possibilities

(1) We can in two ways significantly condense the Script Tips #31-33 Script.

(a) DaysofWeek and Months can be written as condensed arrays or array literals as described in Blog Entry #42, e.g.:

DaysofWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

Months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

(b) The series of if...else statements can be shrunk to four lines of code via the strategy that we applied to the Script Tips #25-28 Script in Blog Entry #61: set the default ender first and then use if statements to set nondefault enders:

ender = "<sup>th</sup>";
if (date == 1 || date == 21 || date == 31) ender = "<sup>st</sup>";
if (date == 2 || date == 22) ender = "<sup>nd</sup>";
if (date == 3 || date == 23) ender = "<sup>rd</sup>";

(2) The HTML sup element maps onto the CSS vertical-align: super declaration, so if you are a stickler for 'separating structure and presentation' (as the W3C would want you to be), then you can (a) put, say,

.supe {vertical-align: super;}

in a header style element or in an external style sheet, and (b) recode the ender statements as

ender = "<span class='supe'>th</span>";
if (date == 1 || date == 21 || date == 31) ender = "<span class='supe'>st</span>"; // etc.

(3) In setting ender, date can, if preferred, be compared to suitable regular expressions instead of numbers:

ender = "<sup>th</sup>";

var x1 = /^(2|3)?1$/;
if (x1.test(date)) ender = "<sup>st</sup>";

var x2 = /^2?2$/;
if (x2.test(date)) ender = "<sup>nd</sup>";

var x3 = /^2?3$/;
if (x3.test(date)) ender = "<sup>rd</sup>";

(It's OK to plug date, a number, into the test( ) method of the core JavaScript RegExp object, which normally takes a string parameter; the "Data Type Conversion" section of the JavaScript 1.5 Core Guide states, JavaScript is a dynamically typed language. That means you do not have to specify the data type of a variable when you declare it, and data types are converted automatically as needed during script execution.)

A lazier approach

If I were teaching a JavaScript course, and a student asked me for a script that creates/displays a text-based date, then I have to confess that I would probably say, "Go use the toLocaleDateString( ) method." All by itself, the toLocaleDateString( ) method of the Date object comes close to reproducing the effect of the Script Tips #31-33 Script: on my iMac,

document.write(new Date( ).toLocaleDateString( ));

writes Saturday January 13 2007 to the page when using Netscape 7.02.

Without taking anything away from the instructive value of the Script Tips #31-33 Script, I find it a bit disappointing that Joe doesn't at least mention the toLocaleDateString( ) method (it's not listed in the HTML Goodies JavaScript methods keyword reference), which was implemented in JavaScript 1.0, the first version of JavaScript. But perhaps I should cut Joe some slack here - most of his readers/students are going to be PC users, and IRT's Date object page notes that the toLocaleDateString( ) method was not supported by MSIE prior to MSIE 5.5, which was released in July 2000 and after Script Tips #31-33 were written.

In the next post, we'll go over a script that displays a random, linking banner and that is only associated with Script Tip #34.

reptile7

Labels:


Friday, January 05, 2007
 
Strange Days of our Lives
Blog Entry #62

The sky is falling! Set the wayback machine, Sherman, for the heady pre-Y2K period, as someone in the HTML Goodies readership requests a script that will calculate the number of days between a given day and January 1, 2000, a day that, with respect to computer systems everywhere, will bring on The Apocalypse and the end of the world as we know it. Or maybe not. Anyway, Script Tips #29 and #30 provide just such a script; it's posted here and it appears below:

<script name="javascript">
// Script by Jari Aarniala (foo@mbnet.fi)

today = new Date( );
y2k = new Date("January 1, 2000");
daysLeft = (y2k.getTime( ) - today.getTime( )) / (1000*60*60*24);
daysLeft = Math.round(daysLeft);
document.write("<center><i>~~ Only " + daysLeft +
" days until the year 2000. ~~</i></center>");
</script>

A script identifier?

Before getting into the meat of the script, we need to take a hard look at the script element start-tag: <script name="javascript">??

Neither the Strict nor the Transitional HTML 4.01 Document Type Definition (DTD) lists a name attribute in its <!ATTLIST SCRIPT> declaration (in the "Document Head" section, near the bottom); the script's author almost certainly meant to use a language attribute here.

This raises an interesting question: what does a browser do when it encounters invalid HTML? Or at least what should it do? Conveniently, Appendix B of the HTML 4.01 Specification has a "B.1 Notes on invalid documents" section that addresses this very topic; regarding the matter at hand, it says:
"If a user agent encounters an attribute it does not recognize, it should ignore the entire attribute specification (i.e., the attribute and its value)."
Of all the script element's attributes, only the type attribute has a #REQUIRED default value designation, i.e., must always be specified (in theory at least); unfortunately, the B.1 section doesn't say anything about a browser's optimal response to a missing #REQUIRED attribute. In any case, on my iMac both Netscape 7.02 and MSIE 5.1.6 execute the Script Tips #29-30 Script, as is, without incident.

It then occurred to me that if we wanted to name a script element for identification purposes, then we could do so with an id="scriptID" attribute; the HTMLScriptElement Interface of the DOM Level 2 HTML Specification lists five attributes that should then be manipulable by a document.getElementById("scriptID").property="suitable_value" statement, even as I've never seen anyone treat a script element as an object in this way.

The Y2K countdown

The Script Tips #29-30 Script begins straightforwardly enough by creating two Y2K 'goalposts' in the form of Date object instances, one for the current day and one for January 1, 2000:

today = new Date( );
y2k = new Date("January 1, 2000");

As far as I am aware, this is the first time we've encountered a parameterized Date object in any of the HTML Goodies JavaScript Primers or Script Tips. The "January 1, 2000" string can alternately be shortened to "Jan 1, 2000" or expressed numerically using a Date(yr_num,mo_num,day_num) format:

var y2k = new Date(2000,0,1);
// The mo_num parameter runs from 0 (January) to 11 (December).

(The Date object can also take numerical hour, minute, second, and millisecond parameters, and if we wanted to, we could set y2k to the very stroke of midnight, January 1, 2000, by supplying 0's for these parameters, but I think you'd agree that this would be overkill.)

The lion's share of the script's work is carried out by the next command line:

daysLeft = (y2k.getTime( ) - today.getTime( )) / (1000*60*60*24);

Lots going on here - let's expand this statement as follows:

var msLeft = y2k.getTime( ) - today.getTime( );
// or var msLeft = y2k - today;
var msPerDay = 1000*60*60*24;
var daysLeft = msLeft/msPerDay;

Here's what's happening:

(1) The value returned by the getTime( ) method is the number of milliseconds since 1 January 1970 00:00:00, Netscape informs us; consequently:
(a) y2k.getTime( ) returns the number of milliseconds spanning y2k and (midnight) January 1, 1970;
(b) today.getTime( ) returns the number of milliseconds spanning today and (midnight) January 1, 1970;
(c) var msLeft = y2k.getTime( ) - today.getTime( ) returns the number of milliseconds spanning y2k and today.
(Algebraically, this is a case of (a - x) - (b - x) = a - b.)

It is in fact not necessary to use the getTime( ) method here. In its "Description" of the Date object, Netscape states, The date [of a Date object instance] is measured in milliseconds since midnight 01 January, 1970 UTC. It follows that y2k - today also returns the number of milliseconds spanning y2k and today.

(2) In the "What a Difference a Day Makes" section of Script Tip #29, Joe says that the 1000*60*60*24 expression is how you write one day in JavaScript. We need to be a lot more specific here: the var msPerDay = 1000*60*60*24 product gives the number of milliseconds in one day.

Come, let us walk down Memory Lane and write it all out as you might have done in middle school science class:

milliseconds/day calculation

(3) msLeft ÷ msPerDay then gives daysLeft, the number of days spanning y2k and today. The unit conversion pathway is shown by the following equation:

daysLeft calculation

As to why the script uses a millisecond-based calculation, Joe remarks, Well, there is no JavaScript command called 'day' so we need to build a day [in terms of milliseconds]. Actually, the Date object has both a getDate( ) method and a getDay( ) method, but these methods have small return ranges - 1-31 and 0-6, respectively - and are thus not useful vis-à-vis the script's purpose. Consider the following code:

var today = new Date("December 31, 2006");
var y2k = new Date("January 1, 2000");
var daysLeft = today.getDate( ) - y2k.getDate( );

daysLeft returns 30 notwithstanding the seven years separating today and y2k.

Displaying the countdown

The following discussion assumes a pre-y2k today (as was the case when the script was written); we'll address a post-y2k today later.

The script then uses the round( ) method of the Math object to round the daysLeft return to the nearest integer, which is assigned to daysLeft:

daysLeft = Math.round(daysLeft);

The resulting daysLeft day count will include the current day if the user visits the page between 12 AM and 12 PM, but will not include the current day if the user visits the page between 12 PM and 12 AM.

In Script Tip #30, Joe alleges that a

daysLeft = daysLeft - 1;

statement after the Math.round(daysLeft) command will exclude the current day from the daysLeft day count, but this is only true for an AM visit; if a user visits the page between 12 PM and 12 AM, then both the current day and the following day will be excluded from the daysLeft day count. Suppose a user visits the page at 3 PM Monday; the Math.round(daysLeft) command effectively takes the user to 12 AM Tuesday, and then the daysLeft = daysLeft - 1 statement takes the user to 12 AM Wednesday. Both Monday and Tuesday are subtracted from daysLeft.

The current day can be more reliably included in or excluded from the daysLeft day count via the ceil( ) and floor( ) methods of the Math object:
var daysLeft = Math.ceil(daysLeft) will include the current day;
var daysLeft = Math.floor(daysLeft) will exclude the current day.

Finally, a document.write( ) command formats and writes to the page daysLeft plus some surrounding text:

document.write("<center><i>~~ Only " + daysLeft +
" days until the year 2000. ~~</i></center>");

We can replace the center and i elements with style declarations as follows:

document.write("<div style='text-align: center; font-style: italic;'>~~ Only " +
daysLeft + " days until the year 2000. ~~</div>");

(The CSS text-align property applies to block-level elements, and can thus be used with a div element but not a span element, which is an inline element.)

A post-y2k today

For a post-y2k today, the Script Tips #29-30 Script as written returns daysLeft as a negative number, which looks kind of weird on the page; there are at least three simple ways we can convert daysLeft to a positive number (i.e., to its additive inverse):

(1) Via the - unary negation arithmetic operator:

// After the Math.round(daysLeft) command, conclude the script with
daysLeft = -daysLeft;
document.write("<div style='text-align: center; font-style: italic;'>~~ " +
daysLeft + " days have passed since the Y2K catastrophe! ~~</div>");

(2) Via the abs(x) method of the Math object, which returns the absolute value of its argument x:

// As an alternative to daysLeft = -daysLeft, use
daysLeft = Math.abs(daysLeft);

(3) Perhaps most intuitively, we can simply commute the y2k and today subtraction operands in the initial daysLeft calculation:

var daysLeft = (today - y2k) / (1000*60*60*24);

It is left to the reader to verify that
(a) for the first two of these ways, replacing the Math.round(daysLeft) command with Math.floor(daysLeft) ensures that the current day is included in the daysLeft day count whereas replacing Math.round(daysLeft) with Math.ceil(daysLeft) excludes the current day from daysLeft, and
(b) vice versa for the third way.

Other date spans

In its Date object "Description," Netscape also says, The Date object range is -100,000,000 days to 100,000,000 days relative to 01 January, 1970 UTC; it follows that we can apply the Script Tips #29-30 Script to the calculation of the number of days spanning any two days within this ≅550,000-year period, for example:

var today = new Date( );
var liz1 = new Date("November 17, 1558");
var daySpan = (today - liz1) / (1000*60*60*24);
daySpan = Math.ceil(daySpan);
document.write("It was " + daySpan +
" days ago today that the Virgin Queen ascended the throne.");

We'll turn to the Date object and its methods once again, and also revisit the topic of arrays, in the next post as we take on the Script Tips #31-33 Script, which creates and writes to the page a text-based date.

reptile7

Labels:



Powered by Blogger

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