reptile7's JavaScript blog
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

Comments: Post a Comment

<< Home

Powered by Blogger

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