reptile7's JavaScript blog
Saturday, July 30, 2011
 
Money and Name RegExp Testing
Blog Entry #222

We continue today our analysis of HTML Goodies' "Checking Your Work: Validating Input -- Getting Started" and "Checking Your Work: Validating Input -- The Cool Stuff" tutorials. At this point our remaining task is to check over the "Cool Stuff" tutorial's regular expression(regexp)-based validation of user input for the text fields of the tutorial guestbook form.

We have previously carried out regexp validations with the test( ) method of the RegExp object and with the search( ) method of the String object (see Blog Entry #49, e.g.) - methods recommended by Mozilla for a simple comparison between a string and a regexp pattern. In contrast, all of the "Cool Stuff" regexp validations employ the match( ) method of the String object, a method we have not used before and which we should discuss before getting under way.

The match( ) game

When a string is successfully matched against a regexp pattern containing parentheses, the string itself and the parts of the string matching the parenthesized parts of the regexp pattern are "remembered" by the JavaScript engine, more specifically, they are organized as an array. There are two JavaScript methods that allow access to this array:
(1) the match( ) method of the String object, and
(2) the exec( ) method of the RegExp object.
(I'm actually not sure whether the array exists prior to the use of these methods or whether the use of these methods creates the array, but for the purposes of this discussion it doesn't really make a difference.)

There are two "Cool Stuff" regexp patterns that contain parentheses:

var telephoneExpression = /^((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}$/;
var moneyExpression = /^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(\.[0-9]{2})?$/;


The telephoneExpression pattern is for vetting the Telephone field. The moneyExpression pattern is for validating a monetary number and is unrelated to the guestbook form - we won't be dealing with this pattern later so we'll use it to illustrate the match( ) method.

With respect to the numbers it accommodates, the moneyExpression pattern can be divided into the following parts:
[0-9]+ matches a number comprising an unbroken string of one or more digits, e.g., 10000.
[0-9]{1,3}(,[0-9]{3})* matches one, two, or three digits followed by zero or more comma-delimited triads of digits, e.g., 2, 20,000, or 200,000,000.
(These subpatterns allow for numbers with leading zeroes, e.g., 0030; we'll sort out this issue when we discuss the numbersOnlyExpression regexp pattern.)
(\.[0-9]{2})? is for numbers with a ¢/p part: it matches a decimal point followed by two digits. The ? quantifier signifies that this part of the match is optional.

So, suppose you are a Web auctioneer who wants to validate user input for a

What is your bid on Item X? $<input type="text" id="userbidID" name="userbidName" />

field with the moneyExpression pattern. The user enters 1,000.07 into the field and submits the value, triggering a function containing a

var moneyArray = document.getElementById("userbidID").value.match(moneyExpression);

statement. What's in the moneyArray return?

Per the preceding discourse, moneyArray is an array (FYI: typeof moneyArray merely returns object, but moneyArray.constructor does return function Array( ) { [native code] }), and it holds four elements:

moneyArray[0] = 1,000.07 - for the user's original string;

moneyArray[1] = 1,000 - for the parenthesized ([0-9]+|[0-9]{1,3}(,[0-9]{3})*) part of the moneyExpression pattern;

moneyArray[2] = ,000 - for the parenthesized (,[0-9]{3}) part of the moneyExpression pattern; and

moneyArray[3] = .07 - for the parenthesized (\.[0-9]{2}) part of the moneyExpression pattern.

Got all that? Now, let's look at the code that the tutorial author offers for validating the userbidID field:

// Validate for money
if (document.getElementById("userbidID").value.match(moneyExpression)) {
    /* Valid - do nothing */ }
else {
    validationMessage = validationMessage + " - Input not a valid money amount\r\n";
    valid = false; }


If the userbidID field's value matches the moneyExpression pattern, then the if clause is operative because the returned match( ) array converts to true in a boolean context; in this situation we do nothing. If the userbidID field's value doesn't match the moneyExpression pattern, then the match( ) operation returns null, which is one of those values (along with undefined, 0, and the empty string) that converts to false in a boolean context, and therefore the else clause is operative; in this case, a new segment is appended to the validationMessage string and the valid flag is set or reset to false.

Do we really need to be using the match( ) method here? You can see for yourself that the preceding conditional only checks if the match( ) return is null or not, and makes no use of the parenthesized parts of the moneyExpression pattern. If all we are interested in is a one-off comparison between the user's input and the moneyExpression pattern, then we can and should trade in this code for a corresponding test( )-based conditional:

if (!moneyExpression.test(document.getElementById("userbidID").value)) {
    validationMessage += " - Input not a valid money amount\n";
    valid = false; }


At least for a successful match between a string and a parentheses-containing regexp pattern, match( ) execution is slower than test( ) execution - see the Description section of Mozilla's test( ) method page. Is there a match( )/test( ) speed difference if the regexp pattern does not contain parentheses or for an unsuccessful match? You wouldn't think so, but I don't know. Even if we were to stick with the match( ) conditional, however, we should still transfer its else commands to the if clause, toggle the if condition with the ! operator, and then ditch the else clause, à la the test( ) conditional.

What's my name?

Getting back to the guestbook form, the "Cool Stuff" tutorial uses a noNumbersExpression regexp pattern and match( ) conditionals analogous to that given above to validate the form's First Name and Last Name fields:

var noNumbersExpression = /^[a-zA-Z]+$/;
// Validate first name and last name are all letters
if (document.getElementById("TextFirstName").value.match(noNumbersExpression)) { /* Valid - do nothing */ }
else {
    validationMessage = validationMessage + " - First name must contain letters only\r\n";
    valid = false; }
if (document.getElementById("TextLastName").value.match(noNumbersExpression)) { /* Valid - do nothing */ }
else {
    validationMessage = validationMessage + " - Last name must contain letters only\r\n";
    valid = false; }


The noNumbersExpression pattern was briefly explained in the closing part of the previous entry; it flags user inputs that do not consist solely of alphabetic characters (its identifier is thus a bit off in that it excludes symbols (e.g., @, #, $) as well as numbers).

It is of course a Webmaster's prerogative to limit a name input to alphabetic characters, but I myself would be less restrictive in this regard. We most recently addressed name validation in Blog Entry #206, in which I noted, [N]ames can contain white space, hyphens, apostrophes, etc. (Within reason, they can even legitimately contain digits and symbolic characters.) Accordingly, I would subtract noNumbersExpression's ^ start-of-string anchor and $ end-of-string anchor and just use a /[a-zA-Z]+/ or /[a-z]+/i pattern.

You may be wondering, "What about the other extreme, can a name legally be a number or symbol(s), without any letters at all?" LegalZoom.com's Name Change Education Center sends out conflicting signals on this score, in effect saying, "Technically yes, but good luck with trying to change your name to such a name" - LegalZoom cites the example of Prince during his unpronounceable symbol days but gives the impression that it's a much tougher row to hoe for an average Joe. Anyway, I feel no need to cater to these oddball cases given their rarity.

Besides circumscribing the composition of a user's name inputs, you might also want to bound the lengths of those inputs - after all, someone could enter into the First Name field supercalifragilisticexpialidocious, which would pass validation, and you probably wouldn't want that. Towards this end, the noNumbersExpression tests can be augmented with fieldObject.value.length > number tests that flag names with more than number characters.

if (!noNumbersExpression.test(document.getElementById("TextFirstName").value)) {
    validationMessage += " - First name must contain one or more letters\n";
    valid = false; }
else if (document.getElementById("TextFirstName").value.length > number) {
    validationMessage += " - First name cannot contain more than number characters\n";
    valid = false; }


If the user's inputs are limited to alphabetic characters, then these tests can be combined by replacing noNumbersExpression's + operator with a {1,number} operator.

if (!/^[a-z]{1,number}$/i.test(document.getElementById("TextFirstName").value)) {
    validationMessage += " - First name must contain letters only, and cannot contain more than number characters\n";
    valid = false; }


Alternatively, we can equip the TextFirstName/TextLastName input elements with a maxlength="number" attribute that will simply cut off their values at number characters, but that doesn't really count as validation, does it?

Vis-à-vis the people I've known, the longest name I've encountered is Pranatharthiharan (17 letters), so I would set number to 20 - seems reasonable, eh?

We'll go through the numbersOnlyExpression, telephoneExpression, and emailExpression regexp validations, and roll out a demo, in the next entry.

reptile7

Thursday, July 21, 2011
 
The Loopless World
Blog Entry #221

In this post we will take up HTML Goodies' "Checking Your Work: Validating Input -- Getting Started" and "Checking Your Work: Validating Input -- The Cool Stuff" tutorials, which are both authored by Curtis Dicken. These tutorials address some basic validation issues as they pertain to the "guestbook" form shown in the following image:

[The 'Checking Your Work: Validating Input' guestbook form]
(I put the word "guestbook" in quotes because the form doesn't have a textarea field for user comments, which IMO is a sine qua non of a guestbook.)

We've done the form validation thing several times previously, and the tutorial material is mostly review but I would still like to go through it for a couple of reasons:
(1) Both tutorials contain 'code bloat' that desperately needs to be tightened up.
(2) The "Cool Stuff" tutorial carries out regular expression-based validations of the form's text input values but provides no deconstruction at all therefor, and we really oughtta do something about that, y'know?

Before we go any further, I should point out a coding mistake that once corrected allows the rest of the code to run unproblematically: the type attribute of the form's button

<input id="ButtonSubmit" type="button" value="Submit" />

is set to button, which makes the button a generic push button and not a submit button; set type="submit" and we're good to go.

Don't leave it blank, part 5

The Creating a Test Form section of the "Getting Started" tutorial presents the guestbook form's HTML. Subsequently a Required Input section provides a script that runs through the text input fields and tests if any of them are blank by comparing the lengths of their values to 0. If one or more of those fieldObject.value.length == 0 comparisons return true, then the script pops up a 'Such and such is missing' alert( ) message and cancels the submission of the form.

The script code is contained by a validateMyForm( ) function that is called by the form's onsubmit="return validateMyForm( );" attribute when the button is clicked. The validateMyForm( ) function begins by initializing
(a) a valid variable for monitoring the validation of the form, and
(b) a validationMessage variable that will hold the aforementioned end-of-validation alert( ) message.

function validateMyForm( ) {
    var valid = true;
    var validationMessage = "Please correct the following errors:\r\n";


These declarations are followed by the validation code for the First Name field:

// Validate first name
if (document.getElementById("TextFirstName").value.length == 0) {
    validationMessage = validationMessage + " - First name is missing\r\n";
    valid = false; }


If the user doesn't enter anything into the id="TextFirstName" field, then - First name is missing\r\n will be appended to the validationMessage string and valid will be toggled to false.

The validateMyForm( ) function continues in this fashion with analogous conditionals for the Last Name, Telephone, Email, and How many years have you been using the internet? fields. At the end of the function,
(1) an if (valid == false) window.alert(validationMessage); conditional displays validationMessage on an alert( ) box if necessary, and
(2) a return valid; statement green-lights or red-lights form submission, as appropriate.

The \r\n at the end of each validationMessage segment translates to a single end-of-line character for most of the OS X browsers on my computer. If you were to leave everything blank, here's what the alert( ) box would look like with these browsers:

[The validationMessage alert( ) if all fields are blank]

IE 5.2.3 for Mac OS X interprets \r\n as two end-of-line characters, thereby giving a double-spaced validationMessage. Replacing \r\n with \n or \n\n should ensure a single-spaced or double-spaced display, respectively.

Is it necessary to have separate conditionals for the various text input fields? Certainly not. These tests are easily automated as follows:

var fieldData = ["First name", "Last name", "Telephone", "Email", "Years using Internet"];
var inputList = document.getElementsByTagName("input");
for (i = 0; i < 5; i++) {
    if (!inputList.item(i).value) {
        validationMessage += " - " + fieldData[i] + " is missing\n";
        valid = false; } }


• We don't need to go all the way out to the length property to check if a field is blank; if the field's value is an empty string, then it'll convert to false in a boolean context, and that false can be toggled to true by the ! logical operator - see the "Logical Operators" page of the Mozilla JavaScript Reference.
• By arraying the differing parts of its segments, the validationMessage string can be assembled automatedly as well.

To supplement the validationMessage alert( ), the author recommends in the tutorial's Adding Error Messages to the Form section that we display a '*Please provide your whatever' message next to each invalid field. These messages would be deployed in initially hidden span elements appended to the fields they are flagging

First Name: <input id="TextFirstName" type="text" />
<span id="errorFirstNameMissing" style="visibility:hidden;">*Please provide your first name.</span><br />


and then visibilized by switching their visibility settings to visible in their validateMyForm( ) validation conditionals.

if (document.getElementById("TextFirstName").value.length == 0) {
    validationMessage = validationMessage + " - First name is missing\r\n";
    document.getElementById("errorFirstNameMissing").style.visibility = "visible";
    valid = false; }
else {
    document.getElementById("errorFirstNameMissing").style.visibility = "hidden"; }

/* The else clause rehides the message upon going from a blank first name value to a non-blank first name value. */

We can again automate the manipulation of span visibility:

var spanList = document.getElementsByTagName("span");
for (j = 0; j < spanList.length; j++)
    spanList.item(j).style.visibility = !inputList.item(j).value ? "visible" : "hidden";

/* The ?: conditional operator is detailed here. */

Finally, the tutorial's Required Input – Dropdown List Box Style section attends to the validation of the form's Country selection list via a validateMyForm( ) conditional that checks if the user has not chosen one of the Canada, France, United Kingdom, or United States country options:

// Validate country selected
if (document.getElementById("SelectCountry").value == "Select your country...") {
    validationMessage = validationMessage + " - Please select your country\r\n";
    valid = false; }


In classical JavaScript the client-side Select object did not have a value property; however, the W3C has equipped the DOM HTMLSelectElement interface with a value attribute, so the if condition comparison is legit.

Automated validation can include the Country selection list
(a) if the fields of the form are accessed via the form's elements[ ] collection, and
(b) if the value attribute of the selection list's Select your country... option is set to an empty string:

var fieldData = ["First name", "Last name", "Telephone", "Email", "Country", "Years using Internet"];
for (i = 0; i < 6; i++) {
    if (!document.forms[0].elements[i].value) {
        validationMessage += " - " + fieldData[i] + " is missing\n";
        valid = false; } }
...
<select id="SelectCountry">
<option value="" selected="selected">Select your country...</option>
...


Checkbox assay

As long as we're tightening things up, let's jump forward to the Custom Validation section of the "Cool Stuff" tutorial and its validation of the Favorite websites? set of checkboxes. To determine whether at least one checkbox is checked, the section codes an if statement whose condition discretely tests the checked property of each checkbox.

if (document.getElementById("CheckboxHtmlGoodies").checked == false &&
document.getElementById("CheckboxWDVL").checked == false && // ... etc.


If the checked returns of the checkboxes are all false, then a relevant string is tacked onto validationMessage and valid is switched to false. This gets the job done but is a bit tedious. Alternatively we can loopically tally the number of unchecked checkboxes and then reset validationMessage and valid if the unchecked count reaches 5:

var unchecked = 0;
for (k = 7; k < 12; k++) if (!inputList[k].checked) unchecked++;
if (unchecked == 5) {
    validationMessage += " - Please select at least one favorite Web site option\n";
    valid = false; }


So far, so normal. However, per its title the section also considers a custom validation issue, one that we have not encountered previously. The Favorite websites? set of checkboxes has a bit of radio button character in that the final None selection and the first four checkbox selections are meant to be mutually exclusive, that is, if you check the None checkbox, then you're not supposed to check any of the other checkboxes. To police this situation, the section appends to the 'if no checkboxes are checked' if statement an else if clause that pits
(a) the checked status of the None checkbox against
(b) the checked status of the first four checkboxes as a group

else if (document.getElementById("CheckboxNone").checked == true &&
(document.getElementById("CheckboxHtmlGoodies").checked == true ||
document.getElementById("CheckboxWDVL").checked == true || // ... etc.


and subsequently resets validationMessage and valid if both (a) and (b) return true.

My own approach to the 'if you choose None, don't choose anything else' validation - an approach that facilely extends to the 'make sure at least one checkbox is checked' validation - is given below:

var checkedOther = false;
for (k = 7; k < 11; k++) if (inputList[k].checked) { checkedOther = true; break; }

var checkedNone = inputList[11].checked ? true : false;

if (checkedOther && checkedNone) {
    validationMessage += " - None should not be selected with other favorite Web site selections\n";
    valid = false; }

if (!checkedOther && !checkedNone) {
    validationMessage += " - Please select at least one favorite Web site option\n";
    valid = false; }




Of course, to merely check if a field is blank or not is to set the bar rather low. Accordingly, the Validating Data Type and Properly Formatted Input sections of the "Cool Stuff" tutorial present regular expressions and accompanying code blocks for vetting the values of the text input fields. But as intimated at the outset of the post, the author unloads this code on the reader without explaining it in any way, and he doesn't exactly encourage the reader to learn more about it:
Don’t worry if the expressions don’t make any sense to you. Creating regular expressions is an art form all its own. Whenever possible you should try to find an expression that someone else has already created and tested. Writing one from scratch can be both time consuming and tedious, especially for the novice.
Admittedly, I can see why he wouldn't want to explicate the emailExpression and telephoneExpression regexp patterns, but he should at least quickly go through the simpler noNumbersExpression and numbersOnlyExpression patterns - for example, he could say, "The ^[a-zA-Z]+$ pattern matches a string that, from start (^) to finish ($), comprises one or more (+) alphabetic characters, lowercase or uppercase (a-zA-Z)" - and then perhaps exhort the reader to do further homework/seek out a suitable reference for the other patterns. Is that too much to ask?

In Blog Entry #117, I stated, To anyone who wants to 'go beyond the basics' of Web coding, I highly recommend a study of regular expressions, which I have found pays major dividends in grappling with the minutiae of the coding world, and I very much stand by this assessment. The best regular expressions resource that I know of (even better than the Regular Expressions chapter in the Mozilla JavaScript Guide) is Jan Goyvaerts' Regular-Expressions.info - do check it out.

We'll put the "Cool Stuff" regexp validations under the microscope in the following entry.

reptile7

Monday, July 11, 2011
 
Registrations, Loads, and Mousemoves
Blog Entry #220

We will today look over HTML Goodies' "JavaScript for Programmers - Events" tutorial, which is an excerpt from a JavaScript for Programmers book written by Paul J. Deitel and Harvey M. Deitel; the tutorial itself is credited to the former author. "JavaScript for Programmers - Events" is divided into three sections:
(1) Registering Event Handlers
(2) Event onload
(3) Event onmousemove, the event Object and this
In support of its topic(s), each section features code whose effect is illustrated with one or more black-and-white screen shots; no demos are provided and it is left to the reader to bring the code to life. That said, I've tried all of this code and can report that it runs smoothly without any modifications on my part.

Register me

The tutorial's first section focuses on the two fundamental ways that an event handler function is "registered" (associated or bound) with an element or object:

(1) <element onevent="handlerFunction( );">, which is termed the inline model (à la inline style attributes, perhaps); and

(2) object.onevent = handlerFunction;, which is termed the traditional model.

I've never seen the inline model and traditional model terms in an official specification - you won't find them in the Events section of HTML5, for example - and I will respectively replace them with "element attribute approach" and "property assignment approach" in the discussion below.

The author claims that the element attribute approach to event handler registration predates the property assignment approach, which is a reasonable conclusion to draw from the relevant sections in the JavaScript 1.0 and JavaScript 1.1 specifications. Some event handlers and the element attribute approach do go back to JavaScript 1.0, the first version of JavaScript, and are described in the Scripting Event Handlers section thereof, whereas the first example of the property assignment approach appears in the Calling event handlers explicitly subsection of the corresponding JavaScript 1.1 section.

But of course, this isn't proof that Navigator 2.0, to which JavaScript 1.0 is 'indexed', doesn't support the property assignment approach; indeed, the following sentence from the JavaScript 1.0 Scripting Event Handlers section suggests that such support just might be there:
However, you can always call an event-handler directly (for example, you can call onClick explicitly in a script).
You can actually still download Navigator 2.x from evolt.org. (The links to Mac versions of Navigator 2.x on the "Netscape 3 & earlier" page of SillyDog701's Netscape Browser Archive are broken, whereas Netscape's own browser archive only goes back to Navigator 4.78.) I downloaded Navigator 2.0 at this page and then installed it in the SheepShaver environment. With the following code

<script type="text/javascript">
window.onload = initButton;
function initButton( ) {
    document.forms[0].button1.onclick = greeting; }
function greeting( ) { window.alert("Hello, World!"); }
</script>
<form action="">
<input type="button" name="button1" value="Click Me">
</form>


Navigator 2.0 duly popped up an alert( ) box displaying Hello, World! upon clicking the push button, so it would seem that the implementations of the element attribute and property assignment approaches were in fact contemporaneous.

The Registering Event Handlers section's code associates click events and a handleEvent( ) function

function handleEvent( ) { window.alert("The event was successfully handled."); }
/* handleEvent is perhaps not the best name for a function given that the DOM's EventListener interface features a handleEvent( ) method. */

with (a) an id="inline" div element using the element attribute approach and (b) an id="traditional" div element using the property assignment approach. I trust we don't need a demo for this; however, you can view the div display here (OK, the div border color isn't #0000bb, but otherwise that's what the divs look like) and the handleEvent( ) alert( ) box here if that'll help you sleep better tonight.

Although JavaScript for Programmers was published in 2009, the tutorial makes no mention of the DOM addEventListener( ) method (first implemented in Netscape 6, which was released in 2000) or Microsoft's proprietary attachEvent( ) method (first implemented in IE 5 for Windows, which was released in 1999) for registering event handlers. It's not as though these methods are difficult or confusing to use:

function registerHandler2( ) {
    var inlineDiv = document.getElementById("inline");
    var traditionalDiv = document.getElementById("traditional");
    if (inlineDiv.attachEvent) {
        inlineDiv.attachEvent("onclick", handleEvent);
        traditionalDiv.attachEvent("onclick", handleEvent); }
    else if (inlineDiv.addEventListener) {
        inlineDiv.addEventListener("click", handleEvent, false);
        traditionalDiv.addEventListener("click", handleEvent, false); } }
window.onload = registerHandler2;


• Even on a Mac, the attachEvent( ) method is supported by Opera.
• The addEventListener( ) method is supported by IE 9+.

Two points of correction before moving on:

(1) The "Common Programming Error 1.1" (don't put a function pointer in quotes) and "Common Programming Error 1.2" (don't put parentheses after a function pointer) at the top of the tutorial's second page apply to the property assignment approach and not to the element attribute approach.

(2) In the section's final paragraph, the author notes that the property assignment approach allows us to register event handlers in JavaScript code[, which] allows us to assign event handlers to many elements quickly and easily using repetition statements [i.e., loops], implying that this is not possible for the element attribute approach. Au contraire: onevent="handlerFunction( );" attributes can indeed be deployed scriptically and iteratively by making recourse to the setAttribute( ) method of the DOM's Element interface:

var elementList = document.getElementsByTagName("elementName");
for (i = 0; i < elementList.length; i++)
    elementList.item(i).setAttribute("onevent", "handlerFunction( );");


Load it

The tutorial's second section briefly discusses the onload event handler and uses it in a script that counts the number of seconds a user has been at a Web page:

Seconds you have spent viewing this page so far: 0

(The above demo employs the tutorial's "Figure 11.2" code mostly as is, cosmetic changes aside.)

The Event onload section's first sentence intimates that onload is an element-wide event handler: classically this might* have been true on the Microsoft side but was definitely not true on the Netscape side, although I see that HTML5 stipulates that onload must be supported by all (X)HTML elements.

*I hedge on this point as I have not been able to find the specifications for the pre-DOM versions of JScript; these 1996-1997 materials are lost in the sands of time, more specifically, they're beyond the reach of the Internet Archive and no one seems to have made a copy of them. In contrast, specifications for all versions of JavaScript can be readily tracked down with a modicum of effort.

The section's allegation that elements in [an (X)HTML] page cannot be [scriptically] accessed until the page has loaded is not true for modern browsers or most older browsers for that matter. As long as a script is placed in the document body (the body element has a (%block;|SCRIPT)+ +(INS|DEL) content model) after element X, it'll be able to reference element X without incident before the document loads; to the best of my knowledge, IE 3 was the last browser that threw an 'element X' is not an object-type error in this situation.

Regarding the section example, the code below gives a functioning demo - no need for an onload event handler at all:

<body>
<p>Seconds you have spent viewing this page so far: <strong id="soFar">0</strong></p>
<script type="text/javascript">
var seconds = 0;
function updateTime( ) {
    ++seconds;
    document.getElementById("soFar").innerHTML = seconds; }
window.setInterval(updateTime, 1000);
</script>
</body>


On the other hand, if putting an element-referencing script in the document head and coordinating it with an onload event handler makes you feel more organized/less cluttered, then I certainly wouldn't want to dissuade you from doing that.

Color trails

We save the best for last. The tutorial's third section briefly discusses the onmousemove event handler and the client-side event object and applies them to the creation of a simple drawing program that will allow you to unleash your inner Jackson Pollock. ;-)

Hold ctrl to draw blue. Hold shift to draw red.

(I've made some minor modifications to the "Figure 11.3" code - for example, I've added a padding:0px; declaration to the td style rule set so as to subtract the padding-top:1px; and padding-bottom:1px; that Safari and Chrome would ordinarily give to the table cells of the canvas - but have otherwise left it intact.)

In the div above, move your mouse cursor to the square canvas below the Hold ctrl to draw blue. Hold shift to draw red. header. Hold down a control key (if you're browsing with Opera for Mac, a command key) and move the cursor around: this should produce a blue trail on the canvas. Holding down a shift key and moving the cursor around should produce a red trail on the canvas. (You may need to first click on the div to transfer focus to it.)

The canvas is a 100-row grid of 10,000 4px-by-4px td elements, 100 cells across and 100 cells down; the grid is created by a 'two-dimensional loop' contained by a createCanvas( ) function.

function createCanvas( ) {
    var side = 100;
    var tbody = document.getElementById("tablebody");
    for (var i = 0; i < side; i++) {
        var row = document.createElement("tr");
        for (var j = 0; j < side; j++) {
            var cell = document.createElement("td");
            cell.onmousemove = processMouseMove;
            row.appendChild(cell); }
        tbody.appendChild(row); } }


The outer loop's tbody.appendChild(row); command appends each row tr element to the content of an id="tablebody" tbody element; Internet Explorer will not render any table cells that are dynamically added to a table outside a thead, tbody or tfoot element, the author explains at the top of the tutorial's third page. Upon commenting out the tbody element and appending the rows to the parent id="canvas" table element (and also upon exchanging the table's border-collapse:collapse; style declaration for a cellspacing="0" element attribute), I find that the drawing program code works fine with IE 5.2.3 for Mac OS X: with this browser it is necessary to simultaneously depress (a) a control key and an option key or (b) a control key and a command key to draw in blue. However, IE 5.2.3 for Mac OS X is not exactly a cutting-edge browser and is not a reliable predictor for how things go on the Windows side.

The inner loop's cell.onmousemove = processMouseMove; assignment associates each cell with a processMouseMove( ) function that respectively colors the cell blue or red if a control or shift key is depressed when the mouse cursor moves over the cell.

function processMouseMove(e) {
    if (!e) var e = window.event;
    if (e.ctrlKey) this.style.backgroundColor = "blue";
    if (e.shiftKey) this.style.backgroundColor = "red"; }


The this object reference plays an all-important role in the processMouseMove( ) function. Here's what the author has to say about this:
To determine which table cell to color, we introduce the this keyword. The meaning of this depends on its context. In an event-handling function, this refers to the DOM object on which the event occurred. Our function uses this to refer to the table cell over which the mouse moved. The this keyword allows us to use one event handler to apply a change to one of many DOM elements, depending on which one received the event.
I had never seen this use of this before (it's not in classical JavaScript), so I appended a window.alert(this); command to the processMouseMove( ) function body and then mousemoved over the canvas: sure enough, the resulting alert( ) box displayed [object HTMLTableCellElement].

Mozilla's addEventListener( ) page includes a section that addresses the value of this inside an event handler function; the section notes that this stands in for the target element if event handler registration is carried out via the addEventListener( ) method

cell.addEventListener("mousemove", processMouseMove, false);

but not if it's carried out via the element attribute approach.

cell.setAttribute("onmousemove", "processMouseMove( );");

Overall the Event onmousemove, the event Object and this section's discussion of the drawing program code is well-written and I encourage you to read through it. The section concludes with a list of cross-browser event object properties. A couple of comments regarding that list:

(1) You should be aware that the Microsoft and Mozilla keyCode properties are not identical; unlike the Microsoft keyCode return, the Mozilla keyCode return differs depending on whether the triggering event was a keypress or a keydown/keyup. The Mozilla equivalent of the Microsoft keyCode property is the which property.

(2) Mozilla states that the cancelBubble property is deprecated and urges you to use the stopPropagation( ) method instead.

Microsoft's event object properties are detailed here; Mozilla's Event interface portal is here.

The next Beyond HTML : JavaScript sector tutorial, "Checking Your Work: Validating Input -- The Cool Stuff", has a "Checking Your Work: Validating Input -- Getting Started" introductory companion that is strangely no longer listed in the sector. These tutorials will return us to the topic of form validation and we'll hit both of them in the following entry.

reptile7


Powered by Blogger

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