reptile7's JavaScript blog
Saturday, April 24, 2010
 
Adventures in Event Cancelation
Blog Entry #177

We previously discussed HTML Goodies' "Checkboxes: Only Two" tutorial in Blog Entry #47 as part of a multipost discourse on the topic of data validation. Per its title, "Checkboxes: Only Two" presents a script that prevents a user from checking more than two checkboxes within a form containing three or more checkboxes. In the "Only two checkboxes, please" section of Blog Entry #47, we overhauled the "Checkboxes: Only Two" script, greatly reducing its volume of code by replacing its series of if statements with a for loop.

We conclude our tour of HTML Goodies' "JavaScript Form Scripts" series of tutorials in today's post by revisiting a 'value-added' aspect of the "Checkboxes: Only Two" script that goes beyond the script's immediate effect: for those events that are cancelable, the script clearly illustrates the syntax for canceling an event via a function. When the target for an event is an HTML element, the code for functionally canceling the event can be written in general form as follows:

<script type="text/javascript">
function checkEvent( ) {
...
[ if (condition) ] return false;
}
</script>
<element onEvent="return checkEvent( );">


In the above code, it is necessary
(a) for the checkEvent( ) function to return false AND
(b) for the element's onEvent handler to contain a return statement to act on the checkEvent( ) return
in order to cancel the event. Alternatively, an

elementObject.onevent = checkEvent;

JavaScript assignment statement can be used instead of the element's onEvent="return checkEvent( );" attribute; for example, Joe could have replaced the onClick="return KeepCount( );" input element attributes in the "Checkboxes: Only Two" script with the following script statement:

/* As top-level code, this statement should be placed in or referenced by a script element that appears after the joe form. */
for (i = 0; i < document.joe.elements.length; i++) { document.joe.elements[i].onclick = KeepCount; }


So, what can we do with our event cancelation code? What all can we cancel?

Cancelation scope and examples

With respect to classical JavaScript's event handlers, the following events can be canceled (in theory, at least):
• Key events: keydown, keypress, keyup
• Mouse events: click, dblclick, mousedown, mousemove, mouseout, mouseover, mouseup
• Form events: reset, submit
• According to Netscape, the dragdrop event should also be cancelable; however, the ondragdrop event handler is not supported by any of the OS X browsers on my computer. (Moreover, I find that Mozilla's updated, addEventListener( )-based method for canceling dragdrop events works with Camino but that's it.)

Given that the keyboard and the mouse constitute standard input for a GUI computer, it follows that we are able to preempt most of the ways a user can interact with a Web page.

Keypress cancelation demo

The demo in this subsection was adapted from this Netscape example. In the div below, the user is asked to enter a five-digit zip code into a text field. Click on the field and try to type a non-digit (letter or symbol) character into it.

Enter your five-digit zip code in the box below:


The demo uses the following script to prevent the keyboard entry of non-digit characters:
<script type="text/javascript">

function blockNondigits(e) {
	var thisKey = e ? e.which : (event ? event.keyCode : "");
	var keyChar = String.fromCharCode(thisKey);
	if (!/\d/.test(keyChar) && thisKey != 8) return false; }

document.zipForm.textentry.onkeypress = blockNondigits;

</script>
Upon attempting to type a character into the name="textentry" text field, the resulting keypress event calls the blockNondigits( ) function. blockNondigits( ) identifies the character of the pressed key via the event object's which or keyCode property in collaboration with fromCharCode( ), a cross-browser static method of the String object. The identified character, keyChar, is then compared to a /\d/ regular expression signifying a [0-9] digit: if they don't match, a return false; statement cancels the keypress event and thereby prevents keyChar from being entered into the textentry field.

The aforelinked Netscape example uses the cancelation of keydown events to prevent the user from typing an a or A character into a text field. I originally wrote the above demo with onkeydown as the blockNondigits( ) trigger and it worked fine with all of the OS X GUI browsers on my computer except for Opera, which 'hears' keydown events but evidently will not cancel them; switching onkeydown to onkeypress solved this problem.

Netscape's example uses the which property of the event object to obtain the ASCII/Unicode code position of the character for a depressed key. The which property is supported by all of the OS X GUI browsers on my computer except for MSIE, which uses an analogous keyCode property for this purpose. As detailed in Blog Entry #143, these different properties and the differing Netscape and Microsoft event models can be reconciled via a var thisKey = e ? e.which : (event ? event.keyCode : ""); conditional.

Regarding the example's var keyChar = String.fromCharCode(e.which); line, Netscape says, In the function, the which property of the event assigns the ASCII value of the key the user presses to the keyChar variable - not true: in fact, the e.which ASCII value is converted by the String.fromCharCode( ) method to the value's corresponding character, which is what is actually assigned to keyChar.

Lastly, to keep the backspace/delete key active, I added a thisKey != 8 subcondition to the declaration of the if statement that compares keyChar and /\d/.

The demo is not foolproof in the sense that non-digit characters can be mousically pasted into the textentry field via the browser's Edit menu.

Canceling link action and Boolean methods

The action of a link can also be conditionalized. In illustration, click on the link below:

Take me to the W3C's home page.

Here's the code, which was adapted from this Netscape example:

<a href="http://www.w3.org/" target="_blank" onclick="return window.confirm('Click the OK button to open the W3C\'s home page in a new window.');">Take me to the W3C's home page.</a>

The confirm( ) method of the window object has a Boolean return: clicking the button on the confirm( ) box returns true; clicking the box's button returns false. No external code is required in this case, although you could use a separate function to enable/disable a link if you wanted to.

The test( ) method of the RegExp object, which we used in the keypress cancelation demo above, also has a Boolean return. If we were to add a <button>Submit your zip code</button> submit button to the keypress demo's form, then equipping the button element start-tag with an

onclick="return /^\d{5}$/.test(document.zipForm.textentry.value);"

attribute would prevent the button from submitting any textentry value that does not comprise five digits.

Don't take my stuff (please...)

Event cancelation can be used to provide for Web content a thin layer of anti-theft protection. For most JavaScript-enabled browsers, the following function expression will stop a user from selecting and copying the text on a Web page:

document.onmousedown = function ( ) { return false; }

Alternatively, you can accomplish the same thing by simply adding an onmousedown="return false;" attribute to the body element start-tag.

What about images, huh? Moving beyond classical JavaScript's event handlers, Microsoft implemented in MSIE 5 a now-cross-browser oncontextmenu event handler that can be used to suppress the right-click or control-click downloading of images via a context menu in an analogous manner:

document.oncontextmenu = function ( ) { return false; }
// Equivalently: <body oncontextmenu="return false;">


Moreover, the preceding onmousedown expression will prevent the download of an image via dragging it to the desktop.

In practice on my computer, the above onmousedown and oncontextmenu expressions work very nicely with Camino, Chrome, Firefox, and Safari (notwithstanding that Mozilla's Gecko DOM Reference associates oncontextmenu with the window object but not the document object Mozilla now associates oncontextmenu with both the window and document objects); the onmousedown code strangely doesn't work with MSIE (it should!) whereas the oncontextmenu code doesn't work with either MSIE or Opera. (Suffice it to say that just because MSIE for Windows supports a given feature does not mean that MSIE for the Mac also supports that feature.)

You can test your browser's support for these effects in the div below:

<title>Canceling Mousedown and Contextmenu Events</title>

Try to select me.

Try to right-click or control-click me: [Image of an 'Admit One' ticket]

Are there ways around these expressions? Of course - in particular, they can be thwarted by simply turning off the browser's "Enable JavaScript" preference. But if you're that terrified about users copying your content, maybe you shouldn't be uploading it to the Web in the first place.

In the name of completeness

There are two HTML-related default browser behaviors that are generated by mouseover events:
(1) Mousing over a link (having an underlying anchor or area element) puts the link's href value in the browser window's status bar.
(2) Mousing over an element with a title attribute pops up a tooltip.

Can these behaviors be suppressed with a return false; statement? "Yes with Opera, no with all other browsers" is the answer on my computer.

W3C event handler references

As HTML element attributes, most of classical JavaScript's event handlers were implemented by the W3C in HTML 4 - see Section 18.2.3 ("Intrinsic events") in the HTML 4.01 Specification. As you would expect, HTML5 will offer an expanded set of event handlers, including oncontextmenu. HTML 4 doesn't say anything about event cancelability; in contrast, the HTML5 Specification (if I'm reading it correctly) seems to stipulate that its various event types must all be cancelable by 'conforming user agents'.

FYI: HTML5 won't include ondragdrop, which is being splintered into a set of subhandlers (ondrag, ondragend, ...), nor onmove, which as an event handler for the window object seems to have been obsoleted by Netscape/Mozilla long ago.

User events are covered in more detail in the DOM Level 2 and Level 3 Events Specifications; these documents unambiguously state which events should and should not be cancelable. The new and cancelable events described by the DOM Level 3 Events Specification, which is currently at the Working Draft stage, are: compositionstart, compositionupdate, compositionend, mouseenter, mouseleave, mousewheel, textInput, and wheel - of these events, only mousewheel appears in the HTML5 Specification.

In the following entry, we'll move on to the next "Beyond HTML : JavaScript" tutorial, "Click... It's Copied!", which discusses a script that copies a text string to the operating system's clipboard. The "Click... It's Copied!" script doesn't work with any of the browsers on my computer, but that doesn't mean we can't chat about it a bit, does it?

reptile7

Saturday, April 10, 2010
 
Brighten the Controls, Take Two
Blog Entry #176

In this post, we'll wrap up our discussion of HTML Goodies' "Pure Magic Form Script" tutorial with a look at the tutorial's "second script". Billed as an "update" to the first "Pure Magic Form Script" script, which we dissected in detail in Blog Entry #174, the second "Pure Magic Form Script" script is an altogether more straightforward and down-to-earth script than is its predecessor. Specifically, the second script dispenses with arcane DOM features and achieves its background-color effects via object.style commands of the type we have long used on this blog. Relatedly, the second script's use of the DOM getElementById( ) method is unnecessary in all but one case, as we'll see below.

The tutorial's The Second Script hyperlink is broken because the target anchor that precedes the tutorial's "The Second Script" section has the wrong fragment identifier: it's specified as <a name="first"></a> when it should be <a name="second"></a>. The second script is posted on a separate page here but, as explained in the introduction of Blog Entry #174, is for readability reasons better viewed via the source of its demo page.

change( ) two

Like the first "Pure Magic Form Script" script, the second "Pure Magic Form Script" script contains an onkeyup-triggered change( ) function that applies background colors to text boxes and activates/disables a reset button in a form. The second script's change( ) function also effects a one-way change to the content of a div element. As in the first script, the text inputs of the second script's form are given a class="bgwhite" attribute, but no use is made of this class nor of the first script's bgyellow class, which is entirely absent from the second script; rather, the second script's change( ) function directly queries and sets the CSS backgroundColor property of the text inputs.

The second script's change( ) function begins with an if statement that handles the 'field's current value is equal to its initial value' situation:
<script type="text/javascript">
changed = 0;
function change(field)
{
	if (field.defaultValue == field.value)
	{
		if (document.getElementById(field.id).currentStyle.backgroundColor != "white")
		{
			document.getElementById(field.id).style.backgroundColor = "white";
			changed--;
		}
	}
If the defaultValue and value of the field in question are the same, then a nested if statement tests if the field background color is not white. If the nested if statement's condition returns true, for example, if field's background color is yellow, then field is given a white background color and the changed state variable is decremented.

Comments

field is itself an object reference, and thus there's no need whatsoever to use the DOM getElementById( ) method or the DOM id property to access the field object; the document.getElementById(field.id) expression(s) can be reduced to simply field in the above code.

• The proprietary currentStyle property/object was implemented by Microsoft in MSIE 5 - go here for its page in the MSDN Library. currentStyle [r]epresents the cascaded format and style of the [parent] object as specified by global style sheets, inline styles, and HTML attributes, quoting Microsoft. Of the OS X GUI browsers on my computer, currentStyle is supported by MSIE and Opera, but not by Camino, Chrome, Firefox, and Safari, which throw an error at this point in the code.

Unlike currentStyle, the standard and more familiar style property/object is not inclusive with respect to the style information it can access; style will not recognize style rules in a style element, for example. However, currentStyle and style are scope-wise equivalent vis-à-vis the second script's change( ) function - they both reflect the style attribute of field's corresponding input element, and that's it - and replacing the change( ) function's currentStyle tokens with style tokens is pretty much all that's necessary to convert the second "Pure Magic Form Script" script to a cross-browser script (there are also a couple of script typos we'll need to deal with - vide infra).

• If you could use a refresher on setting CSS styles dynamically, JavaScript Kit's style object page will get you all sorted out.

• If desired, the if statements can be merged per the previous post:
if (field.defaultValue == field.value && field.style.backgroundColor != "white")
{
	field.style.backgroundColor = "white";
	changed--;
}
Next in the second script's change( ) function we have an else block that handles the 'field's value has been changed' situation:
else
{
	if (document.getElementById(field.id).currentStyle.backgroundColor != "yellow")
	{
		document.getElementById(field.id).style.backgroundColor = "yellow";
		changed++;
	}
}
The above code gives field a yellow background color and increments changed if field's value and defaultValue are not the same AND if field's background color is not already yellow. Like its preceding if clause, the else block can be written as a merged conditional:
else if (field.value != field.defaultValue && field.style.backgroundColor != "yellow")
{
	field.style.backgroundColor = "yellow";
	changed++;
}
The second script's change( ) function then uses the same

field.form.reset.disabled = ! changed;

assignment to activate/disable the reset button that we saw in the first script's change( ) function. Finally, the second script's change( ) function replaces the id="message" div element's Make your changes to the form string with a Click the reset button to go back to the original form text string via the following command:

document.getElementById("message").innerHTML = "<big>Click the reset button to go back to the original form text</big>";

Comments

• It may not be necessary to use the getElementById( ) method to access the second script's text inputs, but we definitely need it or a related DOM method (e.g., the getElementsByTagName( ) method) to access the script's div element.

• We most recently discussed the on-course-to-be-standardized DOM innerHTML property in Blog Entry #162.

• As noted in the "The form and the div" section of Blog Entry #174, the W3C is 'retiring' the big element. I find that the big element is equivalent to a font-size:larger; CSS declaration with all of the OS X GUI browsers on my computer excepting Opera, which renders font-size:larger; text slightly larger than it does big element text.

• On the pm2code.html script page and in the source of the pm2.html demo page, the big element child of the second script's div element is not properly closed:

<div id=message><big>Make your changes to the form<big></div>

Instead of correcting the above mistake, the big element should be removed and replaced with a #message { font-size: larger; } style rule per the preceding comment.

• The above innerHTML command is a one-way assignment: upon restoring the second script's form to its original state via either the change( ) function or the changeall( ) function (which we'll get to momentarily), the div element text does not revert to Make your changes to the form. It is simple enough to give the change( ) function a revert-to-Make your changes to the form capability by replacing its last two statements with:
if (changed) // If changed is 1 or 2, and thus convertible to true:
{
	field.form.reset.disabled = false;
	document.getElementById("message").innerHTML = "Click the reset button to go back to the original form text.";
}
else // If changed is 0, and thus convertible to false:
{
	field.form.reset.disabled = true;
	document.getElementById("message").innerHTML = "Make your changes to the form.";
}
changeall( ) two

Like the first "Pure Magic Form Script" script, the second "Pure Magic Form Script" script contains an onreset-triggered changeall( ) function that redisables the script's reset button and sets the background colors of both script text boxes to white. Here it is:
function changeall(form)
{
	form.reset.disabled = true;
	changed = 0;
	for (var el = 0; el < form.elements.length; el++)
	{
		if (form.elements[el].id != "") /* Some way needed of knowing which to reset -- in this case, only those with an ID. */ 
		{
			document.getElementById(form.elements[el].id).style.backgroundColor = "white";
		}
	}
}
Comments

• You may recall that the first script's changeall( ) function uses the name property of a control, which originated in classical JavaScript but is now part of the DOM's HTMLInputElement interface, to indirectly flag the script's text boxes for setting their background colors; the second script's changeall( ) function uses the DOM id property to do so. However, the size, type, or value property of the HTMLInputElement interface could also be used for this purpose in either changeall( ) function - e.g., if (form.elements[el].size == 14) form.elements[el].style.backgroundColor = "white";.

• Per the preceding comment and the preceding section, the document.getElementById(form.elements[el].id) expression can and should be shrunk to form.elements[el].

• The second script's changeall( ) function does not reset the script's original div element text, but adding a

document.getElementById("message").innerHTML = "Make your changes to the form.";

statement before or after the for loop will take care of that.

HTML detritus

The second script's body element concludes with the following lines of junk code:

<style>
NOBR {color=blue}
</style>
</FONT>
</CODE>
<!-- There are one and two font element end-tags on the pm2code.html page and in the pm2.html source, respectively. Also, in the pm2.html source the font element end-tags and the code element end-tag are separated by the Back hyperlink. -->


The above code should be thrown out (indeed, the color=blue CSS syntax error throws a "Warning" with Firefox and Opera) and doesn't really merit any comment, but while we're here, I should mention that the W3C is also obsoleting the proprietary, Microsoft-implemented nobr element, whose effect can be reproduced by a white-space:nowrap; CSS declaration.

Demo

I thought it would be a good idea to whip up a demo for those of you using browsers that don't support the currentStyle object and/or would like to try out my div text-reversion code. In the div below, click on one or both text boxes and change the text therein in some way; subsequently, return the text to its original value(s) either via the reset button or by deleting/adding the character(s) that you added/deleted initially.

Change and then unchange the text in one or both boxes. Watch the reset button and the text below the form.

Make your changes to the form.

In the next entry, we'll finish our tour of the "JavaScript Form Scripts" series by discussing the event cancelation code in the series' second tutorial, "Checkboxes: Only Two".

reptile7


Powered by Blogger

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