reptile7's JavaScript blog
Monday, August 25, 2014
 
The Hyperlink Shuffle, Part 3
Blog Entry #332

Today's post will conclude our discussion of the New Array Text Pages RandomLinks.html script.

New digs

The JavaScript we've been discussing in the last two entries composes a script element that is the last child of an h3 element:

<h3>Links Are Displayed in Random Order Every Time Page Is Displayed<br><br>
Click "Refresh" to see it work.<br>
<hr align="center" width="100%"><br>
The links can be displayed as a list:<br>
<script type="text/javascript"> ... </script>
</h3>


The h3 text content is horizontally centered by a <center> ancestor that is improperly closed before the end of the h3 element. On my computer, Firefox, Opera, and Safari place the </h3> tag just before the </center> tag upon rendering the RandomLinks.html page.

The h3 element serves a presentational purpose, specifically, it bolds and enlarges its text: the script display does not constitute a 'heading' semantically. Moreover, the h3 element contains an <hr> rule in violation of its (%inline;)* content model. Clearly, a div element would be a more appropriate display container:

#displayDiv { font-size: larger; font-weight: bold; text-align: center; }
...
<div id="displayDiv"> ... </div>


• The div element has a (%flow;)* content model and can thus validly hold an <hr>.
<hr align="center" width="100%"> can be shrunk to <hr>: the hr element's default width is 100%, and if an <hr>'s width is 100% then there's no point in giving it an align setting.

Separating structure and behavior

If you prefer to move the aforementioned script element to the document head and load the URLlist links into a separate, empty div element, then that's pretty easy to do: just replace the

for (i = 0; i < URLlist.length; i++) document.write(URLlist[RandomValue[i]] + "<br>");

loop with the following code:

window.onload = function ( ) { var linkDiv = document.getElementById("linkDiv"); for (i = 0; i < URLlist.length; i++) linkDiv.innerHTML += URLlist[RandomValue[i]] + "<br>"; }
...
<div id="linkDiv"></div>


Starting from scratch

For that matter, if we pre-array the link data

var textArray = ["JavaScripts.com", "Disney", "The Dilbert Zone", "This is True", "Centre for the Easily Amused"]; var hrefArray = ["http://www.javascripts.com", "http://www.disney.com", "http://www.unitedmedia.com/comics/dilbert/", "http://www.thisistrue.com/", "http://www.amused.com/"];

then we can iteratively construct and deploy the links

for (i = 0; i < textArray.length; i++) { var randomLink = document.createElement("a"); randomLink.textContent = textArray[RandomValue[i]]; randomLink.setAttribute("href", hrefArray[RandomValue[i]]); var newLine = document.createElement("br"); linkDiv.appendChild(randomLink); linkDiv.appendChild(newLine); }

and thereby throw out the script's link( ), linkToString( ), and linkArray( ) functions. The createElement( ) method, the textContent attribute, the setAttribute( ) method, and the appendChild( ) method are formally defined/standardized in the DOM Level 3 Core Specification.

Alternatively, we could create the br elements and load the link + newline lines into the linkDiv div via a

linkDiv.innerHTML += randomLink.outerHTML + "<br>";

statement, but if we're going to DOM-ize the code, why not go all the way?

Embed it

The centering <center> element is followed by a block of code that shows how to plant the URLlist links in a normal run of text:

... </center><br>
Or imbed the links (<script type="text/javascript">document.write(URLlist[randomIndex( )]);</script>) in various places in your page.
Here's another link: <script type="text/javascript">document.write(URLlist[randomIndex( )]);</script>.
Another link is: <script type="text/javascript">document.write(URLlist[randomIndex( )]);</script>.
The number of links (<script type="text/javascript">document.write(URLlist[randomIndex( )]);</script>) does not have to be the same as the number of references.


Sample output:

Or imbed the links (Disney) in various places in your page. Here's another link: This is True. Another link is: The Dilbert Zone. The number of links (Javascripts.com) does not have to be the same as the number of references.

We don't need to use the randomIndex( ) function here either if we
(a) print out URLlist[RandomValue[RandomIdx]] and
(b) increment RandomIdx
in each microscript:

/* Recall that RandomIdx is initialized to 0 in the buildArrayofRandomValues( ) function. */
</script>
...
Or imbed the links (<script type="text/javascript">document.write(URLlist[RandomValue[RandomIdx]]); RandomIdx++;</script>) in various places in your page.
<!-- Etc. -->


Can we separate structure and behavior in this case? Yes we can!
(A) Replace each microscript with an empty <span class="linkSpan"></span> element.
(B) Scoop up the spans with a document.getElementsByClassName( ) command.
(C) Load the URLlist links (or links created from scratch) into the spans.

var linkSpans = document.getElementsByClassName("linkSpan"); for (i = 0; i < linkSpans.length; i++) linkSpans[i].innerHTML = URLlist[RandomValue[i]];

There's just one little problem with this approach: the getElementsByClassName( ) method is not supported by pre-9 versions of IE and there are still quite a few IE 8 users out there. Here's a fallback for accommodating IE 6-8 users:
(A2) Wrap the text in an id="linkDiv2" div and replace each microscript with an empty <span>; if there are any other spans in the linkDiv2 div, recode them in some way or get rid of them altogether.
(B2) Scoop up the spans with a document.getElementById("linkDiv2").getElementsByTagName("span") command.
(C2) Load the links into the spans per the preceding loop.

Now, about those links...

(0) http://www.javascripts.com currently redirects to http://www.javascriptsource.com/, the home page of The JavaScript Source.

(1) http://www.disney.com leads to Disney.com, the official home for all things Disney (the actual URL is http://disney.com/).

(2) http://www.unitedmedia.com/comics/dilbert/ currently redirects to http://www.universaluclick.com/comics/dilbert/, Universal Uclick's Dilbert page.

(3) http://www.thisistrue.com/ is fully live: Randy Cassingham's This is True is still going strong.

(4) http://www.amused.com/ is fully dead: Cathie Walker's Centre for the Easily Amused is long gone and its 'past life' is not available via the Internet Archive. However, you can still check in with Cathie at her Facebook page.

In the following entry we'll check over RandomQuotes.html, the fourth New Array Text Pages script, and then start work on TODGreeting.html, the final New Array Text Pages script.

Monday, August 18, 2014
 
The Hyperlink Shuffle, Part 2
Blog Entry #331

Welcome back to our ongoing analysis of the New Array Text Pages RandomLinks.html script. In today's post we'll take a detailed look at the script's buildArrayofRandomValues( ) function.

function buildArrayofRandomValues(length) { RandomIdx = 0; for (i = 0; i < length; i++) { RandomValue[i] = -1; while (RandomValue[i] == -1) { r = Math.floor(Math.random( ) * length); for (j = 0; j <= i && RandomValue[j] !=r; j++) { if (j == i) RandomValue[i] = r; } } } }

The buildArrayofRandomValues( ) function individually populates the RandomValue[0] → RandomValue[4] array boxes with the numbers 0, 1, 2, 3, and 4, but in a random order.

As noted at the end of the previous post, the buildArrayofRandomValues( ) call follows the creation of the URLlist data structure.

var RandomValue = new Array( );
var RandomIdx;
function buildArrayofRandomValues(length) { ... }

var URLlist = new linkArray( ...Link data... );

buildArrayofRandomValues(URLlist.length);


The URLlist length (5) is passed to the buildArrayofRandomValues( ) function and given a length identifier.

The buildArrayofRandomValues( ) body's first statement initializes the RandomIdx variable to 0.

RandomIdx = 0;

The buildArrayofRandomValues( ) function makes no use of RandomIdx, however. RandomIdx plays a starring role in a randomIndex( ) function that we'll briefly discuss later.

The rest of buildArrayofRandomValues( ) comprises a for loop

for (i = 0; i < length; i++) { ... }

whose i counter determines the size of the RandomValue array. The loop does not iterate over the link objects: we'll apply the RandomValue array to those objects when we write the linkToString( ) anchor element(s) to the page.

The loop initializes RandomValue[i] to -1 and then sets in motion a while loop that runs until a new 0|1|2|3|4 number for RandomValue[i] is found.

RandomValue[i] = -1;
while (RandomValue[i] == -1) { ... }


We can throw out the RandomValue[i] = -1; assignment if we change the while condition to RandomValue[i] == undefined.
N.B. Use of a ! RandomValue[i] condition causes the browser to hang. Although undefined converts to false in a boolean context, so does 0, and (without getting into the details) the latter conversion gives rise to an infinite loop.

The while loop begins by getting a random integer in the range 0-4, inclusive; the integer is assigned to an r variable.

r = Math.floor(Math.random( ) * length);

• The Math.random( ) command returns a pseudo-random floating-point number in the range 0 ≤ x < 1.
• The * length multiplication takes us to a 0 ≤ x < 5 floating-point number.
• The Math.floor( ) operation takes us to a 0 ≤ x ≤ 4 integer.

Subsequently another for loop

for (j = 0; j <= i && RandomValue[j] != r; j++) { if (j == i) RandomValue[i] = r; }

uses a j variable to see if r's value has hitherto been loaded into any RandomValue[ ] boxes: if not, then r is assigned to RandomValue[i].

Re the j <= i && RandomValue[j] != r for condition, the <= and != comparison operators take precedence over the && logical operator and thus there is no need to parenthesize the comparisons although you can certainly do so if you feel it would improve the code's readability:

for (j = 0; (j <= i) && (RandomValue[j] != r); j++) { if (j == i) RandomValue[i] = r; }

During the outer for loop's i = 0 iteration, the while loop and the inner for loop each run for one iteration. Let's say the first r return is 3. For j = 0, the j <= i && RandomValue[j] != r and j == i conditions return true and consequently 3 is assigned to RandomValue[0].

Proceeding to the outer for loop's i = 1 iteration, suppose the next r return is again 3. In this case the inner for loop's RandomValue[j] != r subcondition (and therefore the j <= i && RandomValue[j] != r condition as a whole) returns false from the get-go, so the while loop gets another r value.

Suppose the third r return is 1. The inner for loop runs for two iterations; in the j = 1 iteration, j == i returns true and RandomValue[1] is set to 1.

And so on. By and by all five 0/1/2/3/4 numbers come up and are loaded into the RandomValue array. You can see the array value order by putting a window.alert(RandomValue.join( )) command after the buildArrayofRandomValues( ) call.

Display it

When the outer for loop has finished executing and the RandomValue array is complete, we are ready to convert the link objects into bona fide HTML links and then write the latter to the page in a random order.

for (i = 0; i < URLlist.length; i++) { document.write(URLlist[randomIndex( )] + "<br>"); }

A five-iteration for loop calls on a randomIndex( ) function

function randomIndex( ) { RandomIdx++; RandomIdx = RandomIdx % RandomValue.length; return RandomValue[RandomIdx]; }

that increments RandomIdx, modulos RandomIdx by RandomValue.length, and plugs the modulo remainder into RandomValue[ ] so as to access the RandomValue elements in a [1]-[2]-[3]-[4]-[0] order. The randomIndex( ) returns are plugged into URLlist[ ] to give a random series of link objects that are HTML-ized* and printed out via the document.write( ) command.

*The string context of the write( ) command automatically triggers the objects' toString( ) functionality.

The absence of the i counter in the loop body raises a red flag. Why are we reaching for RandomValue with an external function? Loops and arrays are meant for each other: clearly, it would be better to step through RandomValue 'in-house'.

for (i = 0; i < URLlist.length; i++) { document.write(URLlist[RandomValue[i]] + "<br>"); }

The loop displays the link links as a list:

Disney
This is True
The Dilbert Zone
Javascripts.com
Centre for The Easily Amused


The links can also be embedded in the document text: we'll see how to do this in the following entry.

Tuesday, August 12, 2014
 
The Hyperlink Shuffle, Part 1
Blog Entry #330

In today's post we'll take on the most interesting New Array Text Pages script, RandomLinks.html. The RandomLinks.html script creates five links from scratch and prints them out randomly via a shuffled set of array indexes, which is also created from scratch.

Jenson's javascript/ directory offers source code and a demo for a closely related RandomLinks.html script. However, the javascript/ RandomLinks.html code contains some clutter (superfluous functionality) vis-à-vis the Java Goodies RandomLinks.html code and consequently we will work with the latter in the discussion below.

Link building blocks

The textContent and href values for the five links are stored in a URLlist Object object that is created by the following statement:

var URLlist = new linkArray( "Javascripts.com", "http://www.javascripts.com", "Disney", "http://www.disney.com", "The Dilbert Zone", "http://www.unitedmedia.com/comics/dilbert/", "This is True", "http://www.thisistrue.com/", "Centre for The Easily Amused", "http://www.amused.com/");

The statement's right-hand side calls a linkArray( ) constructor function and passes thereto the textContent/href values, which are collectively given an a identifier.

function linkArray( ) { var a = linkArray.arguments; /* The linkArray reference is unnecessary/deprecated. */ ... }

The linkArray( ) function next tests if a.length is an odd number: if true, then an alert( ) message pops up and the length of the URLlist object is set to 0.

if (a.length % 2 != 0) { window.alert("Cannot initialize link array: Incorrect number of strings"); this.length = 0; }

I've never understood the point of statements like this: my reaction is "It's your responsibility as the Webmaster to ensure that the linkArray( ) call contains the right number of arguments, and to test the code before it goes live." Anyway, let's move on. An accompanying else clause

else { for (ti = ai = 0; ai < a.length; ti ++, ai += 2) { this[ti] = new link(a[ai], a[ai + 1]); } this.length = a.length / 2; }

iteratively feeds the ten a arguments to an external link( ) constructor function

function link(description, URL) { this.description = description; this.URL = URL; }

in order to create a set of five child Object objects. The for loop runs for five iterations (ai = 0, 2, 4, 6, and 8): in each iteration, the link( ) function assigns an a[even] value to a description property and the following a[odd] value to a URL property. When the loop has finished executing, the else clause sets URLlist's length to a.length ÷ 2 (5).

Here's the data structure we've got so far:

URLlist[0] = {description: "Javascripts.com", URL: "http://www.javascripts.com"}
URLlist[1] = {description: "Disney", URL: "http://www.disney.com"}
URLlist[2] = {description: "The Dilbert Zone", URL: "http://www.unitedmedia.com/comics/dilbert/"}
URLlist[3] = {description: "This is True", URL: "http://www.thisistrue.com/"}
URLlist[4] = {description: "Centre for The Easily Amused", URL: "http://www.amused.com/"}
URLlist.length = 5;


We're not done yet. The toString( ) method of each link object is overridden with a linkToString( ) function that respectively assigns the object's description and URL to the textContent and href of a stringified anchor element.

function linkToString( ) { return "<a href='" + this.URL + "'>" + this.description + "</a>"; } link.prototype.toString = linkToString;

The objectObject.toString( ) page in the Mozilla JavaScript Reference features an Overriding the default toString( ) method subsection that addresses exactly what we're doing here. The prototype feature functions as a property of the link object type and is what allows us to bind the linkToString( ) function to all five link objects in one go.

FYI: We could have used a buildArray.prototype.toString = quoteOut; statement to override the quote object's toString( ) method in the Quote of the Day (Month) and Quote of the Day (Year) scripts discussed in the previous post. The quote.toString expression did not need a prototype binder; indeed, quote.prototype.toString throws a TypeError.

We will later print out the linkToString( ) return with a document.write( ) command. This being the case, we can code linkToString( ) more cleanly via the link( ) method of the String object:

function linkToString( ) { return this.description.link(this.URL); }

Number mix

Before creating the URLlist object, the script creates an empty RandomValue array and declares a RandomIdx variable.

var RandomValue = new Array( );
var RandomIdx;


After creating the URLlist object, the script calls a buildArrayofRandomValues( ) function.

buildArrayofRandomValues(URLlist.length);

The buildArrayofRandomValues( ) function is the heart of the RandomLinks.html script and deserves its own entry - we'll deal with it next time.

Wednesday, August 06, 2014
 
The Quote Parade, Part 3
Blog Entry #329

We're ready to move on to QuoteOfTheDay.html, the second New Array Text Pages script. Upon visiting the QuoteOfTheDay.html page daily during a given month, the QuoteOfTheDay.html script displays a sequence of quotes, one quote per day, via an i index that maps the quotes onto the dates of the month.

Recall that the CookieQuotes.html script used a buildArray( ) function

function buildArray( ) { var a = arguments; for (i = 0; i < a.length; i++) this[i] = a[i]; this.length = a.length; }

to array a series of 15 quotes; the QuoteOfTheDay.html script uses the same function to array a series of 31 quotes.

var quote = new buildArray( "Lottery: A tax on people who don't understand statistics.", "Smash forehead on keyboard to continue.", ...28 other quotes... "If one synchronized swimmer drowns, do the rest have to drown too?");

We now have a quote Object object with 0, 1, ... 30 numerical properties that are set respectively to the quote strings. Next, the QuoteOfTheDay.html script
(a) creates a currentDate Date object,
(b) gets currentDate's getDate( ) date,
(c) subtracts one from the date, and
(d) assigns the difference to an i variable.

currentDate = new Date( );
i = currentDate.getDate( ) - 1;


Subsequently i is moduloed by quote.length to give a display index for the quote we'll put on the page; the modulo remainder is assigned to i.

i = i % quote.length;

After the modulo statement a conditional compares quote.length and 31: if they're not equal then an alert( ) message to that effect pops up.

if (quote.length != 31) window.alert("Number of quotes is not exactly 31");

We finally plug i into quote[ ] and write the corresponding quote string to the page. The user sees the quote[0] quote on the first day of the month, the quote[1] quote on the second day, etc.

document.write(quote[i]);

(We don't get to the quote[30] quote in months having fewer than 31 days; moreover, February misses out on the quote[29] quote and also the quote[28] quote if the current year is not a leap year.)

Changes to make

(1) Converting the var quote = new buildArray( ...Quote strings... ); instantiation to a dense array constructor or an array literal will allow us to throw out the buildArray( ) function.

(2) The i = i % quote.length; statement can also be sent packing if quote comprises ≥ 31 quotes; however, for a smaller number of quotes you'd need to hang onto it.

(3) Chuck the Number of quotes is not exactly 31 conditional: there's no point in bothering the user with this information if you're working with < 31 quotes.

(4) The quote[i] text is horizontally centered/bolded/enlarged via <center> and <h1> elements: lose this markup in favor of a styled <div> per the Formatting section of the previous post.

A demo

The JavaScript Source hosts a QuoteOfTheDay.html demo here.

Jenson checks in

I have recently discovered that Jenson maintains a home.earthlink.net/~jjcrawford/ Web site with a javascript/ directory that showcases the five New Array Text Pages scripts and four other related scripts. Apropos the present discussion, the javascript/index.html page provides links to
(1) a Quote of the Day (Month) script and
(2) a Quote of the Day (Year) script.
It's not clear if these scripts predate or postdate the Java Goodies QuoteOfTheDay.html script.

Month to month

The Quote of the Day (Month) script displays a different quote on each day of a given month and works with the same series of 31 quotes that the Java Goodies QuoteOfTheDay.html script does. The former defines a quote array à la the latter but follows the buildArray( ) instantiation with:

function quoteOut( ) { currentDate = new Date( ); return this[((currentDate.getDate( ) - 1) % quote.length)]; /* The this keyword references the quote object. */ }
quote.toString = quoteOut;
document.write(quote);


The quote object's toString( ) method is overridden with a quoteOut( ) function that gets the quote of the day as described earlier and returns it to the concluding document.write(quote); command, which calls the quoteOut( )/toString( ) function and prints out its return.

Jenson explains:
The script redefines the toString( ) method for the quote object to make the document.write( ) call easier to use.
But why would we write( ) a re-engineered quote to the page in the first place? The toString( ) method is supposed to return a string representing its calling object. If we are only interested in a particular quote[i] member, then we should output quote[i] per the Java Goodies QuoteOfTheDay.html script.

The year scale

The Quote of the Day (Year) script applies a quote array of 36 quotes to the dates of an entire year.
• The "Back Up My Hard Drive? How do I Put it in Reverse?" quote appears twice.
• The "What we have here is a failure to assimilate.<br>[Cool Hand Locutus]" quote does not appear in the Source Code at the bottom of the QuoteOfTheDayY.html page.

The quote[0] quote is displayed on 1 January; on the following days the remaining quote quotes are individually displayed in sequence until they run out with quote[35] on 5 February; on 6 February the quote sequence begins anew; and so on.

The script obtains the display index for a given date's quote by
(i) tallying the daysSoFarThisYear, the number of days of the year up to and including the present date,
(ii) subtracting one from the tally, and then
(iii) moduloing the difference by quote.length.

return this[(daysSoFarThisYear - 1) % quote.length];

The script obtains the daysSoFarThisYear by adding the current getDate( ) date to the daysBeforeThisMonth, the number of days of the year prior to the present month. The daysBeforeThisMonth for a given month is stored in a daysBeforeThisMonth buildArray( ) object.

var daysBeforeThisMonth = new buildArray(0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335);
var currentDate = new Date( );
var daysSoFarThisYear = daysBeforeThisMonth[currentDate.getMonth( )] + currentDate.getDate( );


The daysBeforeThisMonth[0] value is for January dates, the daysBeforeThisMonth[1] value is for February dates, etc. The daysBeforeThisMonth[2] value, 60, indicates that the preceding February has 29 days; however, the script follows the daysSoFarThisYear determination with a conditional that increments daysSoFarThisYear if the current year is a leap year.

if ((currentDate.getMonth( ) >= 2) && ((currentDate.getYear( ) % 4) == 0)) daysSoFarThisYear++;

• The long-deprecated getYear( ) should of course be replaced by getFullYear( ).
• Years that are divisible by 100 but not by 400 are not in fact leap years, although 2100 is a long way off, eh?

The script wraps the currentDate/daysSoFarThisYear code in a quoteOut( ) function, assigns a quoteOut reference to quote.toString, and write( )s quote to the page.

function quoteOut( ) { /* Create a currentDate; get the daysSoFarThisYear and 'correct' it for a leap year if necessary; return the quote of the day. */ }
quote.toString = quoteOut;
document.write(quote);


Here's how I would revamp the QuoteOfTheDayY.html JavaScript:

var quote = [ ...Quote strings... ];

var currentDate = new Date( );
var currentMonth = currentDate.getMonth( )
var currentYear = currentDate.getFullYear( )

var monthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((currentYear % 4) || (!(currentYear % 100) && (currentYear % 400))) monthDays[1] = 28;


window.onload = function ( ) { var daysBeforeThisMonth = 0; for (i = 0; i < currentMonth; i++) daysBeforeThisMonth += monthDays[i]; var daysSoFarThisYear = daysBeforeThisMonth + currentDate.getDate( ); document.getElementById("quoteDiv").innerHTML = quote[(daysSoFarThisYear - 1) % quote.length]; }

The daysBeforeThisMonth Object has been replaced by a monthDays Array whose elements specify the number of days in each month. The monthDays[1] value is initially set to 29 and is decremented to 28 if the currentYear is not a leap year. The monthDays values are sequentially summed to give a daysBeforeThisMonth tally via a for loop in the window.onload function.

If you go to the trouble of lining up 366 quotes for this script, then the expression for the quote of the day can be simplified to quote[daysSoFarThisYear - 1].

We'll take up the third New Array Text Pages script, RandomLinks.html, in the next post.


Powered by Blogger

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