reptile7's JavaScript blog
Wednesday, July 30, 2014
 
The Quote Parade, Part 2
Blog Entry #328

Welcome back to our discussion of the New Array Text Pages CookieQuotes.html script. Let's take stock of where we are.

Our first visit

For a first-time visit to the CookieQuotes.html page, the script prints out the quoteList[0] quote

Back Up My Hard Drive? How do I Put it in Reverse?

and adds to the CookieQuotes.html document.cookie string an ETUQuoteCount=1 cookie, meaning that we have seen one quoteList quote, per the values of the j index and the i counter; j and i are respectively set to 0 and 1 via the

i = 0; i = i % quoteList.length; j = i++;

set of operations.

We're back

For a second visit, the script increments j and i - more specifically, it extracts (the value of) i from the document.cookie string via the getCookie( ) function and an i = parseInt(i, 10) operation, and then copies i to j and increments i as for the first visit - and as a result prints out the quoteList[1] quote

What we have here is a failure to assimilate.
[Cool Hand Locutus]


and adds an ETUQuoteCount=2 cookie to the document.cookie string. And so on for subsequent visits; on our sixteenth visit, i = i % quoteList.length; gives 0 (as it did for our first visit) and the quote sequence begins all over again.

Script checkup

Construction shmonstruction

The attentive reader may have noticed that I gave relatively short shrift to the buildArray( ) function in the previous post. Deconstruction-wise I could have said
Instantiating the buildArray object type

The script begins by calling a buildArray( ) constructor function and passing thereto a series of quote string arguments, which are collectively given an a identifier. The a arguments are iteratively assigned to numeric buildArray properties...
but I didn't. In part I didn't because we dealt with a very similar Object object creation not so long ago in the course of discussing the Java Goodies Multi-Colored Text script. But there's actually a more important reason that I glossed over the buildArray( ) function: it's excess baggage. The var quoteList = new buildArray( ...quote strings... ); instantiation is easily converted to a dense array constructor or an array literal that doesn't require any external statements, e.g.:

var quoteList = ["Back Up My Hard Drive? How do I Put it in Reverse?", "What we have here is a failure to assimilate.<br>[Cool Hand Locutus]", /* ...Other quotes... */ "A polar bear is a rectangular bear after a coordinate transform."];

i/j movement

For correlating i (and indirectly j) with the length of the quoteList, the i = i % quoteList.length; statement does the job but is too unintuitive for my taste. I prefer to compare i and quoteList.length via a simple if statement:

if (i == quoteList.length) i = 0;

Incrementing the value of i (but not i itself)* in the setCookie( ) function call

setCookie("ETUQuoteCount", i + 1);

allows us to return quoteList[i] and throw j out.

*setCookie("ETUQuoteCount", ++i); and setCookie("ETUQuoteCount", i++); are both problematic.
(f) The former cuts quoteList[0] out of the string loop; the document.write(quote( )); command outputs undefined when i hits 15.
(l) Even worse, the latter shuts down the quote parade altogether by holding the cookie value at 0 and therefore the display at quoteList[1].

A better cookie recipe

In the original script, the setCookie( ) function does not give the ETUQuoteCount cookie an expires value; as a result, the cookie goes up in smoke as soon as the user's browser session is over. The code below will keep the cookie around until the end of next year - that's a long enough cookie lifetime, wouldn't you say?

var myDate = new Date( );
var currentYear = myDate.getFullYear( );
myDate.setFullYear(currentYear + 1, 11, 31);
document.cookie = name + "=" + value + "; expires=" + myDate.toUTCString( );


The setFullYear( ) method of the Date object is detailed here; the toUTCString( ) method of the Date object is detailed here.

In the original script, the value of the ETUQuoteCount cookie is escape( )d when set by the setCookie( ) function and unescape( )d when extracted/returned by the getCookie( ) function. Given that the value is an integer (it doesn't contain any verboten characters) and that we're the ones setting it, however, the escape( )/unescape( ) operations are unnecessary and can be removed.

Formatting

The document.write(quote( )); output is marked up with h1 and center elements. Putting my imagination to work, I can see how a set of quotes could serve as a series of headings. However, if the quotes merely serve a decorative purpose, then they should be housed in a div.

#quoteDiv { font-weight: bold; font-size: 32px; text-align: center; }
...
document.getElementById("quoteDiv").innerHTML = quoteList[i];
...
window.onload = quote;
...
<div id="quoteDiv"></div>


(We know from prior work that the h1 element's initial font-size is 32px.)
You are of course free to style the quoteDiv div per your preference.

Demos

I've found a couple of CookieQuotes.html demos on the Web.

(1) Demo #1 is provided by The JavaScript Source. This demo exchanges the buildArray( ) functionality for a dense array constructor**

var quoteList = new Array( "Back Up My Hard Drive? How do I Put it in Reverse?", "What we have here is a failure to assimilate.<br>[Cool Hand Locutus]", ... );

and doesn't give the ETUQuoteCount cookie an expires value although you can still see the script's effect by refreshing the page repeatedly.

(**Hmmm, I just noticed that the Source Code at the bottom of the CookieQuotes.html page features a dense array constructor vis-à-vis the buildArray( ) functionality.)

(2) Demo #2 comes to us courtesy of Larry "Have Camera Will Travel" Curtis. Larry holds onto the buildArray( ) functionality and gives the ETUQuoteCount cookie a 365-days lifetime.

function setCookie(name, value, days) { var expires = new Date( ); expires.setTime(expires.getTime( ) + 1000 * 60 * 60 * 24 * days); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString( ); }
...
setCookie("ETUQuoteCount", i, 365);


The toGMTString( ) method of the Date object is deprecated, BTW. I don't like millisecond-based Date calculations and would replace the setTime( )/getTime( ) operation(s) with:

expires.setDate(expires.getDate( ) + days); // A lot simpler, huh?

We'll go through the New Array Text Pages QuoteOfTheDay.html script in the following entry.

Wednesday, July 23, 2014
 
The Quote Parade, Part 1
Blog Entry #327

In today's post we'll begin a tour of some or all of the five "New Array Text Pages" scripts offered by the Scripts that Display Text sector of the Java Goodies JavaScript Repository. Authored by Jenson Crawford in late 1998, the New Array Text Pages scripts are united by a common feature: each script uses a constructor function-created Object object to organize content of some sort.

No demos are provided for the New Array Text Pages scripts. An ETUJavaScripts1.zip package holding the scripts may be downloaded here - "ETU" stands for "Easy to Use", BTW. The ETUJavaScripts1.zip package contains the following files:
(1) CookieQuotes.html
(2) QuoteOfTheDay.html
(3) RandomLinks.html
(4) RandomQuotes.html
(5) TODGreeting.html
We accordingly kick off our tour with a detailed deconstruction of the CookieQuotes.html script.

Upon repeated visits to the CookieQuotes.html page, the CookieQuotes.html script displays a sequence of quotes, one quote per visit, via an integer index that is stored and accessed as the value of a cookie - or would do that, if the cookie were given a suitable expires date.

The script begins by arraying a set of quote strings.

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

var quoteList = new buildArray( "Back Up My Hard Drive? How do I Put it in Reverse?", "What we have here is a failure to assimilate.<br>[Cool Hand Locutus]", "I just got lost in thought. It was unfamiliar territory.", /* ...11 other quotes... */ "A polar bear is a rectangular bear after a coordinate transform.");

Its name notwithstanding, the buildArray( ) constructor function does not create an Array object but rather an Object object, as though we had coded:

var quoteList = new Object( );
quoteList[0] = "Back Up My Hard Drive? How do I Put it in Reverse?";
quoteList[1] = "What we have here is a failure to assimilate.<br>[Cool Hand Locutus]";
...
quoteList[14] = "A polar bear is a rectangular bear after a coordinate transform.";
quoteList.length = 15;


functionObject.arguments references have been deprecated: for the a declaration, the arguments object should stand on its own.

var a = arguments;

Mozilla's current arguments page is here.

• For the quoteList object, the 0, 1, ... 14 property names are in fact strings (and not numbers).

Next, the script action moves to a document.write( ) command

document.write(quote( ));

that calls a quote( ) function. The quote( ) function first calls a getCookie( ) function and passes thereto an ETUQuoteCount string.

function quote( ) { i = getCookie("ETUQuoteCount"); ... }

As you might guess, the getCookie( ) function

function getCookie(name) { var search = name + "="; if (document.cookie.length > 0) { offset = document.cookie.indexOf(search); if (offset != -1) { offset += search.length; end = document.cookie.indexOf(";", offset); if (end == -1) end = document.cookie.length; return unescape(document.cookie.substring(offset, end)); } } }

trawls through the CookieQuotes.html document.cookie string in search of a cookie whose name is ETUQuoteCount. (The user's browser could have previously picked up any number of domain- and path-matching cookies at the site hosting the CookieQuotes.html page, but as long as none of those cookies is named ETUQuoteCount, we're OK.)

The getCookie( ) function gives the ETUQuoteCount string a name identifier and appends thereto an = character; the ETUQuoteCount= string is given a search identifier.

If there are any cookies associated with the CookieQuotes.html document, then the getCookie( ) function tries to find search in the document.cookie string; the return for that search is assigned to an offset variable. If the search is successful, i.e., if 0offset, then offset is increased by search.length (14) so that it indexes the first character of the ETUQuoteCount cookie's value.

Subsequently the getCookie( ) function looks for the first post-offset semicolon.
(A) If the ETUQuoteCount cookie's value is immediately followed by a semicolon, meaning that one or more cookies follow the ETUQuoteCount cookie in the document.cookie string, then the semicolon index is assigned to an end variable.
(B) If the ETUQuoteCount cookie's value is not followed by a semicolon, meaning that the ETUQuoteCount cookie is the last cookie in the document.cookie string, then end is set to document.cookie.length.

Finally, the offsetend cookie value is extracted, unescape( )d, and returned to the quote( ) function, which gives it an i identifier.

The unescape( ) and escape( ) (vide infra) functions are both deprecated; more importantly, the script's use of these functions is wholly unnecessary - more on this later.

The offset != -1 if condition returns false for a first-time visit to the CookieQuotes.html page. However, there's no else clause to handle the offset == -1 situation: in this case undefined is returned to the quote( ) function.

Getting back to the quote( ) function, an if...else statement
(a) sets i to 0 or
(b) converts i from a numeric string to a bona fide number
depending on whether i is or is not equal to null.

if (i == null) i = 0;
else i = parseInt(i);


• The if clause was meant to flag a first-time visit; fortunately, undefined == null does return true.

• Mozilla exhorts authors to always specify a radix argument when using the parseInt( ) function.

i = parseInt(i, 10);

Alternatively, the string-to-number conversion can be carried out via the Number( ) function.

The i integer is then moduloed by quoteList.length.

i = i % quoteList.length;

Geek Gumbo has written up a really good modulus operator page that notes:
What if the divisor is bigger than the dividend? Then the value of the modulo is the same as the dividend.
12 % 16 = 12
Didn't know that, I have to confess. It follows that i % quoteList.length gives 0 for a first-time visit.

The modulo remainder, which will serve as a display index for the quote we put on the page, is for the moment assigned to i. The i value is
(i) copied to a j variable and then
(ii) increased by one
via a postfix incrementation.

j = i++;

We're ready to set a cookie that will keep track of how many quotes the user has seen. Toward this end, the quote( ) function calls on a setCookie( ) function that adds an ETUQuoteCount=i cookie to the document.cookie string; i is escape( )d before it is incorporated into the cookie.

setCookie("ETUQuoteCount", i);
...

function setCookie(name, value) { document.cookie = name + "=" + escape(value); }

The quote( ) function concludes by mapping j onto the corresponding quoteList quote and returning quoteList[j]

return quoteList[j];

to the document.write(quote( )); command, which writes quoteList[j] to the page.

We'll briefly recap in the following entry and then see if we can improve the script a bit.

Thursday, July 03, 2014
 
Parsing for Colorables
Blog Entry #326

Welcome back to our analysis of the Java Goodies Rainbow Text script.

The rest of the morefun( ) function

Having stored the value of the myarea textarea field in a mystring variable and opened a new mywin window in our last episode, we are now ready to
(a) color the text characters of the mystring string,
(b) further mark up the resulting text as necessary, and finally
(c) display our handiwork in the new window.
These operations are iteratively effected via a while loop whose i counter moves us through the length of the mystring string.

var i = 0;
while (i != mystring.length) { ... }


The loop first gets the 0th mystring character and assigns it to an a variable.

a = mystring.substring(i, i + 1);

What happens next depends on whether a is a < (left angle bracket) character, a space character, or a normal text character to be colored: these possibilities are respectively addressed by the three branches of an if...else if...else construct.

If it's a tag

if (a == "<") { z = i; while (a != ">") { z++; a = mystring.substring(z, z + 1); } a = mystring.substring(i, z + 1); mywin.document.writeln(a); i = z + 1; }

If a is a <, then the script assumes that it has hit the beginning of an <element> tag. The script uses a z index and a while loop to walk a through the tag until a reaches the tag's concluding > character, at which point the script extracts the tag and writes it to the page. The clause's last statement advances i to the mystring position one past the >.

An analogous tag extraction is carried out by the Java Goodies Multi-Colored Text script: see the Tag encounter section of Blog Entry #307 for its deconstruction.

If it's a space

else if (a == " " || a == "" || a == "  ") { mywin.document.write(a); i++; }

If a is a " " - a space generated by a space bar - then a is written to the page and i is moved to the next mystring position.

I have no idea what the a == "" and a == "  " tests are about. Are they for flagging newlines perhaps? With the browsers on my iMac, a newline does not go through the else if gate but is handled by the code in the next subsection.

The "" and "  " strings were what they seemed to be - an empty string and two spaces, respectively - when I probed them with the String object's charCodeAt( ) method: "".charCodeAt( ) returned NaN whereas "  ".charCodeAt(0) and "  ".charCodeAt(1) both returned 32.

I was originally going to replace the three else if subconditions with a single /^\s*$/.test(a) test.
• In a RegExp pattern, \s matches any white space character.
• The test( ) method of the RegExp object is detailed here.
However, the Multi-Colored Text script doesn't deal with spaces separately but rather 'colors' them along with the rest of the text, and I am now inclined to throw out the else if clause altogether.

If it's normal text

else { if (b > 13) { b = 0; } c = myColor[b]; mywin.document.write(a.fontcolor(c)); b++; i++; }

If a is neither a < nor a space, then the b variable is plugged into the myColor array to give a corresponding six-digit hexadecimal RGB color, which is assigned to a c variable. The c color is applied to a via a fontcolor( ) command and the colored a is written to the page. Subsequently b and i are incremented; when b reaches 14 it is reset to 0 in the next iteration. It follows that the a characters are colored according to a myColor[0], myColor[1], myColor[2], ... myColor[13], myColor[0], myColor[1], ... cycle as the while (i != mystring.length) loop iterates.

Note that b is not reset to 0 if morefun( ) is re-called; consequently, if the user closes the mywin window and clicks the button to open another mywin window, the new mystring display will have a different color pattern (unless mystring contains 14n colorable characters: n = 1, 2, ...).

The fontcolor( ) operation wraps a in a <font color=c></font> element. If you're not happy about that - the font element is currently deprecated and will be entirely obsolete in HTML5 - and would rather place a in a span element and color it with the CSS color property, then the command you want is:

mywin.document.write("<span style='color:" + c + ";'>" + a + "<\/span>");

Post-color

The while (i != mystring.length) loop runs until i equals mystring.length, at which point control passes to an if statement that appends </body> and </html> tags to the mywin document if the status of the isHTML checkbox is false.

if (document.myform.isHTML.status == false) { mywin.document.writeln("</body>"); mywin.document.writeln("</html>"); }

As noted last time, the above if condition returns false - the document.myform.isHTML.status operand returns undefined, which is not converted to false for a == comparison - so the browser moves to morefun( )'s final command:

mywin.document.close( );

According to the W3C, document.close( ) closes a document stream opened by document.open( ) and forces rendering. In practice, Netscape 2-4 does need the preceding command to reliably display the colored mystring string in the mywin window* but modern browsers don't.
(*I find that a &block; input (e.g., <p>This is a paragraph.</p>) is OK but an &inline; input (e.g., <i>This text is italicized.</i>) doesn't show up at all if the document is not closed.)

Demo

In the textarea box below, input
(a) some pure text or
(b) some marked-up text, up to and including an entire document, and then
click the button.



Limitations
<script> and <style> elements won't have any effect (or at least they don't on my computer) - if any are present I'd take 'em out.
• Character references (e.g., &pound; for the £ character) are treated as literal text.
• A < character that is not balanced by a > character (e.g., a < is my favorite symbol mystring) will give rise to an infinite loop.

I didn't like the HTML Source checkbox business so I threw it out. The Background selection list works with all inputs and now includes Gold Background and Pale Green Background options. (I'm a big fan of the #eeffee color as it makes me think of mint chocolate chip ice cream. :-))

The title problem

Let's suppose that
(a) we're working with the original script and
(b) we check the isHTML checkbox and
(c) our textarea input features a <title>Hi</title> title.

The title element has a (#PCDATA) -(SCRIPT|STYLE|META|LINK|OBJECT) content model, meaning that it should contain only text and no child elements. When the Hi content is run through the mywin.document.write(a.fontcolor(c)); command, the font element markup (vide supra) will be treated as literal text and the actual title bar output will look like:

<font color="#990099">H</font><font color="#CC00FF">i</font>
<!-- Your color values may be different, depending on the H's location in the mystring string. -->


(The same thing happens to the Some text content of a <textarea>Some text</textarea> input; if you're gonna put a textarea element in the mystring string, leave it blank.)

The simplest way to get the unadulterated Hi in the title bar is to fish it out of the mystring string and assign it to mywin.document.title at the end of the morefun( ) function:

var titleStartTagStart = mystring.indexOf("<title");
var titleStartTagEnd = mystring.indexOf(">", titleStartTagStart);
var titleEndTagStart = mystring.indexOf("</title>");
var titleValue = mystring.substring(titleStartTagEnd + 1, titleEndTagStart);
mywin.document.title = mywin.document.title ? titleValue : "Somewhere over the rainbow....";
/* The ?: conditional operator is documented here. */



Powered by Blogger

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