reptile7's JavaScript blog
Saturday, April 30, 2011
 
Everything You Always Wanted to Know About Fieldsets (But Were Afraid to Ask)
Blog Entry #213

In the Introduction section of HTML Goodies' "JavaScript and HTML Tricks" tutorial, the author gushes, My Web programming has never been the same since I learned how to use fieldsets to make beautiful forms in Web pages. The tutorial's second page appropriately begins with a Using a stylish form fieldset section that applies a fieldset and a companion legend to a six-control login form.

<form>
<fieldset>
<legend>Please enter your Yahoo! Login Information</legend>
...Two text fields, three radio buttons, and a submit button laid out in a four-row table (see the background-image demo below)...
</fieldset>
</form>


Fieldsets are not new to us: we've been conversant with them since the A 'structured' form section of Blog Entry #93. Nonetheless, the Using a stylish form fieldset section, short as it is, contains several odd and/or incorrect statements that do need to be sorted out.

Fieldset history
One of the coolest form elements built into HTML is the fieldset. It relies on the browser to create a look that can't be replicated using ordinary HTML commands [emphasis added].
In a markup context, the fieldset element has long constituted an ordinary HTML command. The fieldset element was proprietarily implemented by Microsoft for MSIE 4 and shortly thereafter was standardized in HTML 4; its Netscape/Mozilla support goes back to Netscape 6. The now-a-Recommendation HTML5 specification specifies the fieldset element here.

Fieldset appearance
It has the sophistication of images ...
This is nonsense: in its basic form, a fieldset is nothing more than a box drawn around a set of controls, as you can see for yourself from the tutorial's fieldset demo, which refreshingly works at both the HTML Goodies and WebReference versions of the tutorial*. Further examples of basic fieldsets can be seen in HTML Goodies' "Fieldsets and Legends" tutorial.

(*Not so long ago this was not so, as HTML Goodies formerly imported a Yahoo! reset-min.css file that zeroed out the fieldset frame via a fieldset { border: 0; } style.)

But a fieldset can in fact be "stylish", that is, it can be jazzed up with some CSS. We recently worked through a tutorial, "How to Populate Fields from New Windows Using JavaScript", that did just that; more specifically, this tutorial sets CSS border and background (and padding) properties for the form fieldset on this page.

It occurred to me that if we can give a fieldset a background color, then we can give it an 'image wallpaper' via the CSS background-image property:

Please enter your Yahoo! Login Information
User name
Password
Security

Much cooler than the plain vanilla fieldset in the tutorial, eh?

The yellow_fabric.gif image that wallpapers the above fieldset was taken from Netscape's "The Background Sampler" (a resource brought to my attention by HTML Goodies' "So, You Want A Background, Huh?" tutorial), which is no longer hosted by Mozilla/Netscape but is archived at various sites on the Web, for example, the W3C has posted it here.

Fieldset content/parent model
A fieldset must be contained in a form. If you want to use one elsewhere, just surround it with form tags, provided that you're not interrupting another form, of course.
The second sentence implies that a fieldset element is not confined to having control element 'children' (more precisely, descendants - the tutorial code itself intersperses table/tr/td/th elements between its fieldset and control elements) but can contain other types of elements, and this is indeed the case.

In both the HTML 4.01 Strict and Transitional DTDs, the fieldset element has a (#PCDATA,LEGEND,(%flow;)*) content model.

• Regarding the #PCDATA part of the model, the fieldset element declaration is prefaced by the following cryptic comment:
<!-- #PCDATA is to solve the mixed content problem, per specification only whitespace is allowed there! -->
I have no idea what mixed content problem the W3C is talking about. Anyway, to my understanding the content of a fieldset element is supposed to begin with either whitespace - most often, an end-of-line character - or with nothing at all - an empty string counts as #PCDATA - but not with bona fide text.

• After the #PCDATA, a fieldset element must contain a legend element, or at least this is true for a valid document. However, the legend element can also be set to an empty string: <legend></legend>.

• Finally, the model ends with zero or more %flow; units (I would say "elements" but %flow; includes #PCDATA). The flow entity encompasses both block-level and inline elements, meaning a fieldset's post-legend content can be just about anything.

The fieldset element is a block-level element with an effective width of 100% (i.e., it spans the width of the viewport); as such, it makes a nice frame for p element text:


In suits at common law, where the value in controversy shall exceed twenty dollars, the right of trial by jury shall be preserved, and no fact tried by a jury shall be otherwise re-examined in any court of the United States than according to the rules of the common law.


<fieldset style="background-color:#eeffee;border:1px solid red;">
<legend style="display:none;"></legend>
<p>In suits at common law...</p></fieldset>


In contrast, directly equipping a p element with a border/background gives a more claustrophobic rendering:

In suits at common law, where the value in controversy shall exceed twenty dollars, the right of trial by jury shall be preserved, and no fact tried by a jury shall be otherwise re-examined in any court of the United States than according to the rules of the common law.


Getting back to the blockquote at the beginning of this section, the first sentence - A fieldset must be contained in a form - is not true. The body element has a (%block;|SCRIPT)+ +(INS|DEL) content model and the fieldset element is a %block; element (vide supra), and therefore a fieldset element can be a child node of the body element just like a div or p element can. (Controls don't need to be in a form - why should a fieldset?) In corroboration, I find that a form-less fieldset element can be run through the W3C's markup validator without incident, i.e., no errors are thrown.

More generally, the following elements can contain a fieldset element by virtue of having %block; or %flow; in their content models:

blockquote, body, dd, div, fieldset (yes, fieldsets can nest), form, ins/del, li, map, noscript, object, th/td

The above list excludes the button element, whose (%flow;)* -(A|%formctrl;|FORM|FIELDSET) content model excludes the fieldset element.

The form element itself has a (%block;|SCRIPT)+ -(FORM) content model, and thus the Using a stylish form fieldset section's final sentence - One form can contain as many fieldsets as you wish - is correct: see the "doctor's office" example in the W3C's fieldset/legend documentation.

Besides the fieldset and legend, the login form holds a table of controls, including three radio buttons whose labels play a starring role in the next tutorial section, Using Labels to Create Checkboxes and Radio Buttons With Clickable Descriptions, which we'll put under the microscope in the following entry.

reptile7

Friday, April 22, 2011
 
Stochasm Addict
Blog Entry #212

We return now to our discussion of HTML Goodies' "JavaScript and HTML Tricks" tutorial. In our last episode, we stored a multiline list of domain names in the value of a domains hidden control and then converted that value to a lexicographically sorted domainList array. We're ready to move on to the tutorial's Randomizing Data with JavaScript section, which presents a short script for randomly ordering domainList's values.

randomComparison = function (a, b) {
    return Math.random( ) - 0.5; }
domainList.sort(randomComparison);


This code is to be substituted for the domainList.sort( ); line in the Getting Data From Text Lines in Hidden HTML Elements script. Subsequently, a showList(domainList); command displays the randomized domainList à la the lexicographic domainList.

Here's what the author has to say about the randomization code:
The comparison function returns a value greater than zero or less than zero depending on which element should be sorted first. When the sorting algorithm is executed the data will be randomized if the comparison function gives each comparison of elements an equal probability of being greater than zero or less than zero.
The second sentence is OK but the first sentence has the cause and effect of the comparison function reversed - it should read: "The comparison function returns a value greater than zero or less than zero, and that return, in turn, determines whether a given array value x is given a lower or higher array index than another value y to which x is being compared." But perhaps we should back up and begin at the beginning, so to speak.

sort( )ing with a comparison function

We are used to seeing functions act on strings, numbers, and objects. Somewhat unusually, the sort( ) method of the core JavaScript Array object can optionally input a function that directs the sorting process. sort( ) calls this function, and feeds to it two array values to be compared, over and over again in a looplike manner until the sort is complete.

The sort( ) function parameter can take the form of a pointer to the function

arrayObject.sort(functionReference);

or be a function declaration itself.

arrayObject.sort(function (a, b) { ... });

It's not necessary to explicitly call the comparison function: sort( ) does that automatically (imagine there's a
ValuePairObject.oncompare = functionReference;
association statement in the code).

Most comparison functions act on their arguments[0] and arguments[1] values in some way, and it is common practice to parameterize those values respectively with a and b identifiers - cf. the illustrative examples in JavaScript Kit's "Sorting a JavaScript array using array.sort( )" tutorial. Conversely, if a comparison function does not act on its arguments, as is the case for the "JavaScript and HTML Tricks" randomComparison function, then the a/b parameters can be left out.

The relationship between the return of the comparison function and the resulting relative array indexes of two compared values x and y is detailed in the Description section of Mozilla's sort( ) page. Recalling that the random( ) method of the core JavaScript Math object returns a pseudo-random floating-point number ranging from 0 (inclusive) to 1 (exclusive), here's how that relationship relates to the randomComparison function:

• If Math.random( ) - 0.5 gives a negative number, then the a value will be placed earlier (given a lower array index) than the b value in the new domainList. In the showList( ) join( ) display, a will appear higher than b.

• If Math.random( ) - 0.5 gives a positive number, then the a value will be placed later (given a higher array index) than the b value in the new domainList. In the showList( ) join( ) display, a will appear lower than b.

(It is possible for Math.random( ) - 0.5 to give exactly 0, in which case Mozilla recommends that user agents leave a and b unchanged with respect to each other, but sorted with respect to all different elements, but the odds of this happening are too small to worry about.)

The randomized domainList display can be seen at the WebReference version of the tutorial but is once again throttled at the HTML Goodies version by the braces-and-square-brackets-shouldn't-be-escaped problem identified in the previous post. Here's what you should see - the display is augmented with a button for your convenience.

Result


Just like shuffling an iTunes track list, eh?

The modified randomComparison function below offers some insight into the sorting process:

var count = 0;
randomComparison = function(a, b) {
    count++;
    var x = Math.random( ) - 0.5;
    if (x <= 0) { a = a + " (L)"; b = b + " (H)"; } /* (L) is for Lower index, (H) is for Higher index. */
    else { a = a + " (H)"; b = b + " (L)"; }
    document.write("(" + count + ") " + a + ", " + b + ", " + x.toFixed(3) + "<br />");
    return x; }


Sample output:

(1) 123abc.us (L), 0011.us (H), -0.112
(2) 1mans.com (L), 0011.us (H), -0.251
(3) 1mans.com (L), 123abc.us (H), -0.215
(4) hesbest.com (H), 123abc.us (L), 0.234
(5) hesbest.com (H), 0011.us (L), 0.472
(6) 9900.us (L), 123abc.us (H), -0.134
...


As tallied by the count variable, randomComparison typically runs for 40-50 iterations; each output line above summarizes one iteration. In general, an initial order is established for the first three domain names in the unsorted domainList (0011.us, 123abc.us, and 1mans.com) and then the remaining domain names are 'played off' against that order.

The Randomizing Data ... section concludes with some technical remarks on sorting algorithms that are of limited use to the weekend silicon warrior, but there is one sentence in those remarks that I wanted to comment on:
As far as I know, Safari is the only browser which may have an unstable sorting algorithm, resulting in the domain uuuuu.us being sorted to the last position most of the time, although other domains appear to be randomly placed.
I can't vouch for other platforms, but on my iMac uuuuu.us isn't sorted to the domainList[15] position any more often than any of the other domain names when using Safari.

I was going to discuss the tutorial's "fieldset" topic in this post, but I've changed my mind - let's deal with it next time.

reptile7

Thursday, April 14, 2011
 
Down in the Hidden Mine
Blog Entry #211

In today's post we'll take up HTML Goodies' "JavaScript and HTML Tricks" tutorial. Authored by Joseph Myers and originally appearing at WebReference.com, "JavaScript and HTML Tricks" briefly runs through a grab bag of topics relating to lists or forms in some way. Here's what we've got on tap:
(1) The tutorial's first page (a) addresses the storage and extraction of list data in invisible HTML elements and then (b) shows how to randomize that data.
(2) The tutorial's second page discusses (a) the HTML fieldset element, (b) labels as a tool for checking radio buttons and checkboxes, and (c) the use of images for list item markers.
Some of this stuff we've covered previously but it won't kill us to revisit it.

Hide and extract

The tutorial's Introduction touts the use of hidden HTML elements as a great way to store various types of data. The subsequent Getting Data From Text Lines in Hidden HTML Elements section fleshes out this concept with an <input type="hidden"> control whose value holds a set of domain names:

<form>
  <input type="hidden" id="domains" value="
  0011.us
  123abc.us
  1mans.com
  hesbest.com
  9900.us
  herclothing.info
  findher.info
  allme.us
  dropoff.us
  cyou.us
  o-1.us
  meadowlark.us
  uuuuu.us
  os-i.org
  supergreat.us
  nicewebsite.us
  // There are 16 domain names in all.
  " />
</form>


When I first saw this, I thought, "If you don't want the user to see your raw data, then you shouldn't clutter the document body with it - you should just code it as an array from the get-go," but it subsequently occurred to me that an input element is at least a semantically appropriate container for data that will later be fed to a script of some sort. Of course, this is not to say you couldn't use some other element here - for example, you could store the domain name list in an identical format as the content of a span element - and then "hide" (zero out) that element with a display:none; style declaration so as to duplicate a hidden control's rendering (on my computer, X<input type="hidden" value="someValue">Y displays as XY - there isn't even a space between the X and the Y).

As shown above, a multiline hidden control value allows the enumeration of list data, and the annotation of that data with relevant comments, in a highly readable fashion. But as the previous paragraph implies, data is meant to be acted on: how might we extract the domain name list from the value in a useful way? Towards this end, the author applies to the value a series of functions that converts the value to a sorted array of domain names. More specifically:
(1) A getLinesFromHidden( ) function converts the value to an unsorted array of data lines lacking end-of-line characters.
(2) A trimWhiteSpace( ) function removes the two space characters that begin each line.
(3) A removeEmpty( ) function would remove empty data lines if any were present (we'll modify this function below so that it removes // comments as well).
(4) Finally, the array is sorted lexicographically by the sort( ) method of the core JavaScript Array object.
A quick summary of this code is given at the beginning of the Getting Data ... section but a detailed deconstruction therefor is not provided, so perhaps we should do that.

The extraction action kicks off with a call to the getLinesFromHidden( ) function:

function getLinesFromHidden(a) { ... }
...
domainList = getLinesFromHidden("domains");


domains, the id value of the hidden control, is passed to getLinesFromHidden( ) and given an a identifier. getLinesFromHidden( )'s first statement gets the hidden control and gives it an e object reference:

var e = document.getElementById(a);
/* This means of access (vis-à-vis a document.forms[formIndex].elements[0] reference) obviates the need to wrap the hidden control in the form element, which can be thrown out. */


The preceding command is followed by a conditional that would return an empty array if e were null, i.e., if the getElementById( ) call doesn't find the element it's looking for:

if (!e) return [ ];

This sort of code strikes me as pointless: "It's the author's responsibility to see to it that somewhere in the source there's an element having the id in question" is my attitude. Anyway, let's move on to the next line, which gets the hidden control value and gives it an s identifier:

var s = e.value;

We're almost ready to split the hidden control value into an array of data lines. To facilitate that split, the value lines' end-of-line characters, which will vary depending on the user's operating system, are harmonized by the following statement:

s = s.replace(/\r\n|\r/g, "\n");

\r and \n are JavaScript special characters for a carriage return and a newline (a.k.a. line feed), respectively. The value end-of-line character(s) for Windows users, Mac OS X and Linux users, and Classic Mac OS users will be \r\n, \n, and \r, respectively - see the "How to Transfer Text Files Between Linux, Macintosh, and Microsoft Windows Operating Systems" article at WebSiteRepairGuy.com for a straightforward discussion of this topic.

/\r\n|\r/ is a regular expression literal whose pattern matches either \r\n or \r. Using the replace( ) method of the core JavaScript String object, the above command runs through s and replaces all occurrences (note the g flag that follows the literal) of \r\n or \r with \n; the resulting string is assigned to s.

Subsequently, s is split at every instance of the \n separator via the split( ) method of the core JavaScript String object

return s.split("\n");

and the resulting array of data lines is returned to the getLinesFromHidden( ) function call and given a domainList identifier.

Moving along, the next statement

domainList = removeEmpty(trimWhiteSpace(domainList));

initially calls the trimWhiteSpace( ) function and passes thereto domainList. Here's the trimWhiteSpace( ) function:

function trimWhiteSpace(a) {
    var i;
    for (i = 0; i < a.length; i++)
        a[i] = a[i].replace(/^\s+|\s+$/g, "");
    return a; }


After giving domainList an a identifier, trimWhiteSpace( ) loops through the a data lines and replace( )s leading and trailing white space with empty strings. /^\s+|\s+$/ is a regular expression literal whose pattern matches either
(x) one or more white space characters immediately following the ^ start-of-string anchor, or
(y) one or more white space characters immediately preceding the $ end-of-string anchor.
The literal's g flag effectively converts the pattern's | boolean OR operator to a boolean AND, i.e., it enables us to match (x) and (y).

In our example, the a data lines don't have any trailing white space although they individually begin with two space characters, which are removed by the replace( ) operation. The resulting lines are loaded into a new a array, which is returned to the domainList = removeEmpty(trimWhiteSpace(domainList)); line, which next calls, and passes the a array to, the removeEmpty( ) function:

function removeEmpty(a) {
    var b = [ ], i;
    for (i = 0; i < a.length; i++)
        if (a[i])
            b[b.length] = a[i];
    return b; }


removeEmpty( ) loops through a's data lines and loads the non-empty ones into a new b array. b is initially declared as an empty array literal; each assignment to b[b.length] effectively increments the b.length array index by 1. Empty a[i]s are left behind as they convert to false as an if condition. The b array is returned to the domainList = removeEmpty(trimWhiteSpace(domainList)); line and renamed domainList.

Removing comments

Getting rid of single-line // comments is easy; at the removeEmpty( ) stage, recasting the loop conditional as

if (a[i] && !/^\/\//.test(a[i])) b[b.length] = a[i];

will keep //-beginning lines out of the b array. As you would expect, a / in a regular expression must be literalized with a backslash when using the literal syntax.

Getting rid of multiline /* */ comments is trickier, particularly if such comments extend over more than two lines; nonetheless, this can be done at the getLinesFromHidden( ) stage via a

s = s.replace(/\/\*[\S\s]*?\*\//g, "");

statement placed just before or after the s = s.replace(/\r\n|\r/g, "\n"); line.

Regarding the \/\*[\S\s]*?\*\/ regular expression:
• The * characters of the comment delimiters must of course be literalized with backslashes.
[\S\s] matches any character, including \n, which is not matched by the 'dot'.
• If the s in question has more than one /* */ comment, then it is necessary to make the * quantifier of [\S\s]* "non-greedy" by following it with a ? - otherwise, everything between those comments will be removed as well. The ?-modulation of regular expression quantifiers is briefly discussed at the Mozilla JavaScript Guide's "Regular Expressions" page to which I've been linking but I also encourage you to check out Regular-Expressions.info's "Repetition" page, which treats this subject in helpful detail.

// comments can also be removed at the getLinesFromHidden( ) stage via a

s = s.replace(/\/\/.*/g, "");

statement placed just before the return s.split("\n"); line.

Sort and display

The domainList = removeEmpty(trimWhiteSpace(domainList)); line is followed by a

domainList.sort( );

command that sorts the domainList array in lexicographic order, i.e., à la a dictionary (e.g., allme.us follows the domains that begin with a digit, o-1.us precedes os-i.org). The sorted domainList is finally displayed via a showList( ) function that
(1) joins the domainList values into one big string and separates those values with br elements via the join( ) method of the core JavaScript Array object,
(2) wraps the join( )ed string in a blockquote element, and
(3) document.write( )s the blockquote element to the page.

d = document;
function showList(a) {
    d.write("<blockquote>", a.join("<br />"), "</blockquote>"); }
showList(domainList);


The printed result appears at the WebReference version of the tutorial but not at the HTML Goodies version. Inspection of the latter's document source does reveal the presence of code for a demo just before the "Later, I'll write an article ..." paragraph; exasperatingly, this code is defective because its brace and square-bracket script characters are specified in the form of numeric character references: { is coded as &#123;, } is coded as &#125;, etc. (This isn't the first time we've seen a script killed in this way at HTML Goodies.) Moreover and less importantly, the <h3>Result</h3> heading and the

div.result { border: #ccf 2px inset; padding: 0px; margin: 1em; width: 250px; float: right; }
div.result h3 { background: #ccf; margin: 0px; padding-left: 10px; }


style that WebReference gives to its demo are missing in the corresponding HTML Goodies code. Here's what you should see:

Result


In the following entry, we'll randomize the domainList array and maybe also cover one or two of the tutorial's form-related topics.

reptile7

Monday, April 04, 2011
 
Pong It
Blog Entry #210

Before we get rolling, let me correct a mistake in the previous post. Just before the Animation #1 section, I said, At no point does the tutorial detail the ball.gif image's underlying HTML; in fact, that HTML does appear at the very end of the Animation #4 code on the tutorial's third page, sort of: the id value (ball2) is wrong and the src attribute points to a .png image and not a .gif image, but it is there. (Curiously, at the WebReference version of the tutorial, the second page works with a ball.png image but the third page works with the ball.gif image.) However, I still feel that this code should be specified earlier and given greater prominence in the tutorial.

We continue today our analysis of HTML Goodies' "How to Create a JavaScript Animation" tutorial. We pick up the conversation at the Defining The Area Of Motion section of the tutorial's second page, which presents the tutorial's third animation example.

Animation #3

Like Animation #1, Animation #3 moves the ball.gif image horizontally from left to right. Unlike Animation #1, Animation #3 stops the image movement at the right edge of the document/viewport (not "the screen"); towards this end, Animation #3 supplements the goRight( ) function with an animBall( ) function that conditions the image movement on the position of the image's left edge. The animBall( ) function relies on a separate computeWin( ) function to determine the width of the document/viewport; we'll see below that the computeWin( ) function is superfluous and contains a serious mistake that would pose a problem for the tutorial's fourth animation if the clientHeight property were not as widely supported as it is currently.

Clicking the Animation #3 Bring the Ball Here... demo anchor drops the image to top:2660px; at the HTML Goodies version of the tutorial and to top:2060px; at the WebReference version of the tutorial (the demo works at both versions, but it's easier to follow at the latter - the 2660px value for the former is larger than it should be). Clicking the adjacent Try It link

<a href="javascript:window.setInterval('animBall( )', 80);">Try It</a>

calls the animBall( ) function every 80 milliseconds.

function animBall( ) {
    imgLeftInt = parseInt(document.images["ball1"].style.left);
    ... }


animBall( ) first extracts the integer at the beginning of the image's style.left value and then gives that number an imgLeftInt identifier. animBall( ) next reads the width of the image and gives that number an imgWidth identifier.

imgWidth = parseInt(document.images["ball1"].width);

The parseInt( ) operation is unnecessary* as document.images["ball1"].width references a DOM property with a long (number) data type and not a CSS property with a string data type.
(*It's also inefficient: what actually happens is that parseInt( ) initially converts the document.images["ball1"].width value to a string and then extracts the original number from that string - see the Description section of Mozilla's parseInt( ) page.)

The next animBall( ) line reads the width of the document/viewport and gives that number a winWidth identifier.

winWidth = parseInt(computeWin( ).windWidth);

This line calls the computeWin( ) function, which
(a) equips the window object with a custom, cross-browser windWidth property for getting the width of the document/viewport, and then
(b) returns the window object.
The windWidth value has a number data type so we don't need a parseInt( ) operation here either.

Here's the computeWin( ) function:

function computeWin( ) {
    if (document.body.clientWidth) {
        this.windWidth = document.body.clientWidth;
        this.windHeight = document.body.clientHeight; }
    else {
        this.windWidth = window.innerWidth;
        this.windHeight = document.innerheight; }
    return this; }


The author discusses the computeWin( ) function in very general terms:
What if we want [the ball] to go to the edge of the screen [document/viewport] and just stop? Well, for that, we need to know where the edge of the [document/viewport] is. Fortunately, there are ways of finding out. The bad news is not all browsers use the same methods to tell us what we need to know.

Notice that we don't bother asking what kind of a browser the user has. That's just a good way to write code that becomes obsolete as soon as you post it to your site. Instead, we ask whether the browser understands what we need. If it doesn't understand the first time, we assume it uses the second method to give us the answer.
No Microsoft-vs.-Netscape specifics of any kind are provided - that's where we come in.

For MSIE 4, Microsoft introduced clientWidth and clientHeight properties for respectively getting the width and height of an object/element; these properties can be applied to most HTML elements. When applied to the body element, clientWidth and clientHeight respectively retrieve the width and height of the document content area; if the document content area does not exceed the viewport, then document.body.clientWidth and document.body.clientHeight will respectively return the viewport's width and height. At about the same time, Netscape implemented in JavaScript 1.2 innerWidth and innerHeight properties for the window object that directly measure the viewport's width and height, respectively. All of these properties are on track to be standardized in a CSSOM View Module specification.

computeWin( )'s if clause initially tests browser support for document.body.clientWidth; if we've got that support, then document.body.clientWidth and document.body.clientHeight are respectively assigned to custom this.windWidth and this.windHeight properties. The author only describes the this reference as an object; however, a window.alert(this); command reveals that this takes on the identity of the window object ([object DOMWindow] is displayed on the alert( ) box).

Once upon a time, document.body.clientWidth would have returned undefined and thus would have converted to false as an if condition for non-MSIE users, but this is no longer the case. All of the OS X GUI browsers on my computer support document.body.clientWidth and document.body.clientHeight; moreover, I can confirm via tests in the SheepShaver environment that Netscape/Mozilla support for these properties began with Netscape 7.

For its part, computeWin( )'s else clause assigns window.innerWidth to this.windWidth but assigns document.innerheight to this.windHeight. innerHeight is not and has never been a property of the document object, and its H is capitalized; the capitalization mistake is corrected in the balls.js script but the document reference is not. Nevertheless, if we were using a browser for which the else statements were operative, then the document.innerHeight line would not interfere with Animation #3, whose horizontal movement calls on the windWidth property but not the windHeight property.

(window.innerWidth and window.innerHeight are supported by most of the non-MSIE browsers on my computer, and it is simple enough to comment out the if clause and the else { } container and force these browsers to go through the else statement pathway. The document.innerHeight line does give rise to strange behavior at the bottom of the document for Animation #4 - I'll spare you the details.)

The this references in the computeWin( ) function are unnecessary: windWidth and windHeight could be defined as freestanding variables, which would effectively make them properties of the window object. But windWidth and windHeight are themselves excess baggage; indeed, we can throw out the entire computeWin( ) function if we define winWidth and a corresponding winHeight via:

winWidth = document.body.clientWidth ? document.body.clientWidth : window.innerWidth;
winHeight = document.body.clientHeight ? document.body.clientHeight : window.innerHeight;


Anyway, with imgLeftInt, imgWidth, and winWidth in hand, let's move to the conditional that concludes and lies at the heart of the animBall( ) function:

if (imgLeftInt < (winWidth - imgWidth)) {
    goRight( ); }
else {
    window.clearInterval(t); }


The ball.gif image has a width of 30 pixels. The (winWidth - imgWidth) expression in the if condition effectively defines a 30-pixel-wide 'stop zone' abutting the right edge of the document content area. As long as the left edge of the image (imgLeftInt) is to the left of the stop zone, then the image is moved rightward by the goRight( ) function:

function goRight( ) { document.images["ball1"].style.left = parseInt(document.images["ball1"].style.left) + 5 + "px"; }

When the image's left/right edges coincide with those of the stop zone, the else clause halts the movement. (In practice, the ball stops a bit beyond the stop zone, or at least that's what I see on my computer.)

Try it out below - click the button and then the button:



Animation #4
Let's put it all together. We can make the ball go up and down, left and right, and random speeds. We can start the animation and stop it with a click.
Animation #4 significantly ramps up the complexity of the ball.gif movement.

• Building on Animation #3, the author introduces binary dirx/diry variables that determine the direction of motion

function animBall(on) { // No use is made of the on argument.
    ...
    if (dirx == 1) // dirx = 1 signifies left-to-right motion.
        goRight( );
    else // dirx = 0 signifies right-to-left motion.
        goLeft( );
    if (diry == 1) // diry = 1 signifies up-to-down motion.
        goDown( );
    else // diry = 0 signifies down-to-up motion.
        goUp( );


and send the ball in the opposite direction when it hits a given edge of the document content area.

if (imgLeftInt > (winWidth - imgWidth)) { // If the image goes beyond the document's right edge...
    dirx = 0; // Send it leftward.


• In Animations #1, #2, and #3, the ball moves at a predetermined, uniform speed. In Animation #4, a setRand( ) function

function setRand( ) {
    randnum = Math.floor(Math.random( ) * 6) + 2;
    return randnum; }


sets a random value for the ball's initial speed (more precisely, the Δx part of the ball's Δx/Δt)

var spdx = setRand( );
...
function goRight( ) {
    document.images["ball1"].style.left = imgLeftInt + spdx + "px";


and sets a new random value for the ball's speed when it changes direction.

if (imgLeftInt > (winWidth - imgWidth)) {
    dirx = 0;
    spdx = setRand( ); }


The setRand( ) function is explained in the A Little Random Action section of the tutorial's second page, to which I would make a minor, nitpicking correction: both Mozilla and Microsoft note that Math.random( ) can in fact return 0.

• Whereas Animations #1, #2, and #3 are purely horizontal animations, Animation #4 simultaneously combines horizontal and vertical movement. Accordingly, the goRight( ) and goLeft( ) functions are supplemented with corresponding goDown( ) and goUp( ) functions that iteratively get and set the ball's style.top value.

As intimated at the outset of the post, the full Animation #4 code is reproduced on the tutorial's third page. It's easier to grab the code at the WebReference version of the tutorial; click the "view plain" link at the top of the display and then copy the code from the window that pops up. For whatever reason, at the HTML Goodies version of the tutorial the code was taken out of its original textarea container, which would have made it easy to copy, and annotated with never-used line numbers, which must be removed before execution.

Directly above the code are Click here and Start Animation links for activating an Animation #4 demo. Clicking the Click here link visibilizes the ball at a position:absolute;left:10px;top:10px; style address. At the WebReference version of the tutorial, clicking the Start Animation link calls the Animation #4 animBall( ) function, which is not externalized but appears in the document source, every 80 milliseconds via a window.setInterval( ) command. At the HTML Goodies version of the tutorial, the document source does not include or otherwise call on the Animation #4 animBall( ) function but, embarrassingly, imports the same balls.js script that is imported by the preceding page; as a result, clicking the Start Animation link repeats the Animation #3 demo via calling the Animation #3 animBall( ) function.

Even at the WebReference version of the tutorial, and assuming the computeWin( ) if clause to be operative (vide supra), the demo is a bit of a chore to follow because
(a) the demo motion is bottom-bounded by document.body.clientHeight and
(b) the document height greatly exceeds the viewport height.
So I guess I should give you my own demo.

 
At any given time, the goRight( ) or goLeft( ) function is running in tandem with the goDown( ) or goUp( ) function. The horizontal and vertical components of the ball's motion are completely independent of one another - that's why the ball moves as it does. Besides making the recommended changes of the Animation #3 section above, I have recast dirx/diry as dirX/dirY à la the DOM's coordinate-based properties (e.g., pageX, screenX) and, drawing inspiration from the HTML dir attribute, switched their 1/0 values to ltr (left to right)/rtl (right to left)/utd (up to down)/dtu (down to up) as appropriate, but have otherwise left the original Animation #4 code alone.
The next Beyond HTML : JavaScript sector tutorial, "Using Multiple JavaScript Onload Functions", presents an addLoadEvent( ) function for triggering multiple functions with a single load event and without successive window.onload = functionPointer; statements overwriting one another. My first impression was that the addLoadEvent( ) code is a solution in search of a problem, but the author states, We use it over at the JavaScript Source for all of our scripts requiring the onload event handler. After that, we have "How to Use a JavaScript Query String Parser", which was covered in Blog Entries #195 and #196. After that is "JavaScript and HTML Tricks", which we'll check over in the following entry. reptile7


Powered by Blogger

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