Looking for the previous guiStuff?

It's still here, the content didn't go anywhere. You may want to check out this new guiStuff though -- It's rather informative.

References/Tutorials:


Intro Documents:


guiStuff:






::Stuff for the multi-spec coder;

Coding, formats, standards, and other practical things.

 Home  //  JavaScript  //  Events: Part 2 

<!-- JavaScript

Events: Part 2

In Events: Part 1, we got to the point where we constructed the initial cross-browser functions that determined for themselves which Event Model to accommodate. In this article, I'll expand these functions, and cover additional features of the advanced Event Models.

Controlling Event Flow

As mentioned when the advanced event models were initially introduced, a single action may trigger multiple event handlers, by multiple elements. Both advanced event models allow you to controll this 'flow' of events. To understand the importance of this feature, let's see the three rectangle example again:

Some Text


If you click the middle rectangle, three events are triggered. If you click anywhere on the green rectangle, two events are triggered. What if you wanted to isolate an area from other, containing areas, so that in some cases one click would mean only one event trigger? You have the ability, in both event models, to stop the 'ascension' of an event up the document tree at any given point. Remember that in the 'mixed advanced model' (the one that accomodates both the DOM Level 2 and the Internet Explorer Models), events only Bubble Up the document tree. Think of this as a type of 'bubble catcher', where you're telling the event to stop bubbling up any more.

To do this, however, it's the first time when we've had to actually capture the event. We literally have to gain access to the event, and "tell" it to stop.

Accessing the Event Object

The Event Object itself is involved and has many aspects. It contains information about the element, and about the mouse and keyboard at the time of the event. Event Objects, broadly speaking, are only created when an event occurs, and are passed to the event handlers, for 'handling' (hence the name: Event Handlers). After all event handlers have finished executing, the Event Objects are destroyed.

Introducing the entire Event Object, in each of the Event Models at the moment would simply be too much information. Instead, I'll introduce features by-implementation. Right now, for example, we're just looking for a way to tell it to stop Bubbling Up the document tree, and in order to do that, we have to gain access to it first.

We'll start with how the DOM approaches this. Here, the problem is solved in a very straight-forward fashion: Event Objects are 'given' to event handlers as arguments. Although we haven't used this yet, every event handler we've written could have accessed the Event Object very simply:

function myHandler(objEvent){ alert('Ping!'); alert(objEvent.type); }

The function you see here is simply an event handler, one who's name you'd pass to addEventListener() or attachEvent(). While you didn't tell any of them that your function had an argument that needed passing, the DOM implies that the first argument is always the Event Object. In this case, I called it objEvent. Often, you'll see that programmers like calling it just e, although I personally don't like variable names that aren't iterators to be one-character long, but that's just personal taste. This may seem a bit abstract at the moment, but I'll put it in context as soon as I cover the IE model.

In Internet Explorer, the Event Object is always at the same place: window.event. While this seems odd, there are quite a few things to be said in favor of an Event Object that 'stands still'. JavaScript doesn't 'thread' processes, which means it always only does one thing at a time. So IE takes the event and places it at one spot, always updating it to reflect the event that's being handled at the moment. This means you don't have to pass the Event Object as an argument, and you don't have to worry about 'where you left it', it's always in the same place.

In IE, the example above would look like this:

function myHandler(){ alert('Ping!'); alert(window.event.type); }

Ok, so all of this is nice philosophical discussion, but how do we create one function that will, well, function the same way in both browsers?

We condition:

function myHandler(objEvent){ alert('Ping!'); if(!objEvent){objEvent = window.event;} alert(objEvent.type); }

If nothing was passed to objEvent an argument, it means that we're using the IE model. If that's the case, there's no harm in placing a reference to window.event in objEvent. Let's combine this with the previous cross-browser function we created:

<span id="def456">Click Me!</span> <script type="text/javascript"> var clicker = document.getElementById("abc123"); addHandler("click", myElement, myHandler); function myHandler(objEvent){ alert('Ping!'); if(!objEvent){objEvent = window.event;} alert(objEvent.type); } </script>

Finally, let's see this in action:
Click Me!

As you may have deduced, the type method of the Event Object, in both models, will return the type of the event. This is useful for when you want to check that you've indeed captured the correct event (you can assign the same event handler to different event types).

Now that we can access the Event Object, we want to tell it to stop Bubbling upward at a certain point. This, unsurprisingly, is done in a different fashion in each model.

In the DOM Event Model, you would use the stopPropagation() method of the Event Object:

objEvent.stopPropagation();

Whereas in the IE model, the window.event object has a cancelBubble property which you would set to 'true':

window.event.cancelBubble = true;

So once again, we have to detect the Event Model, and act accordingly:

function myHandler(objEvent){ if(!objEvent.stopPropagation){ objEvent = window.event; objEvent.cancelBubble = true; } if(objEvent.stopPropagation){objEvent.stopPropagation();} alert("Bubbling Stops Here!"); }

Notice that to check for the IE Event Model, I checked for the lack of the objEvent.stopPropagation method. This is because checking objEvent.cancelBubble will yield 'false', even if the variable is there, because objEvent.cancelBubble's default value is 'false'. Also notice that I first placed window.event into a variable (the same objEvent that was use earlies), so that at a later point it would be easier to access it using cross-browser methods.

So let's see this in action. First, we'll take the three rectangles we've used previously. Then we'll apply event handlers using the cross-browser methods, and then we'll have one of these handlers stop the Bubbling of the event:

function yellowClicked(objEvent){alert("block_yellow was clicked");} function greenClicked(objEvent){alert("block_green was clicked");} function blueClicked(objEvent){ if(!objEvent.stopPropagation){ objEvent = window.event; objEvent.cancelBubble = true; } if(objEvent.stopPropagation){objEvent.stopPropagation();} alert("block_blue was clicked -- Bubbling Stops Here!"); } var block_yellow = document.getElementById("block_yellow"); var block_green = document.getElementById("block_green"); var block_blue = document.getElementById("block_blue"); addHandler("click", block_yellow, yellowClicked); addHandler("click", block_green, greenClicked); addHandler("click", block_blue, blueClicked);

See what happens when you click the middle rectangle, as apposed to clicking the green one:

Some Text


Now you're able to nest elements within one another, add event handlers to each of them, and decide for yourself if you want the events to keep flowing up the document tree, and up to which point. This is vital in Web-based applications, where you're trying to create intricate user interfaces without sacrificing the portability and the inherent cross-platform nature of the Web.

Speaking of application-like functionality...

Capturing the Mouse

One of the most powerful features that the higher-level Event models affords you, and without a doubt one of the most fun features to play with, is the ability to track the mouse pointer within the browser. Virtually at any given time you can query the browser and 'ask' for the coordinates of the mouse pointer, given, of course, that it's within the document. More specifically, you can query the Event Object for this information. What this means is that an event has to take place, at which point an Event Object is created, at which point you can ask that object for the mouse coordinates. It is therefor a good thing we have the onmousemove event to attach to elements around the page (including the document itself).

First let's set up a playground:

<style type="text/css"> #mousetracker { width: 200px; height: 200px; border: 1px solid #DDD; background-color:#FFF; padding: 15px; } </style> <div id="mousetracker"> <form id="mainform"> <input type="text" id="field_one" name="field_one" /> </form> </div>

This is simply a text field within a form, within a DIV. It looks like this:



Now before we get into how you'd go about capturing the mouse pointer's coordinates, I'd like to properly introduce the mousemove event. When you assign a mousemove event handler to an element, it means that whenever the position of the mouse pointer moves, by any amount, in any direction, within the area of that element, a mousemove event occurs. This is not a method with which to "count pixels". Pointing devices have many different behaviours, and even standard mice are not guaranteed to move by one pixel increments. It's likely that if you changed your device settings, you could get your mouse pointer to move several pixels even when it's performing the smallest move it can register.
The mousemove event technically occurs whenever the pointer changes its position within the element, while staying within the element. This means it occurs many times over. To give you an idea of how rapidly mousemove events take place, move your mouse over the square below:


As you've probably guessed, the square DIV was assigned a mousemove event handler which incremented a variable by 1 each time the event occurred. That variable was then assigned to the form field. Here's the script:

var counter = 0; var zone_a = document.getElementById("mousetracker"); addHandler("mousemove", zone_a, actCount); function actCount(){ document.forms.mainform.field_one.value = counter; counter++; }

That should give you some idea as to the rapidity of the mousemove event. So why am I stressing this so much? Because it's a common mistake to add event handlers that react to mousemove and then proceed to execute 7-pages worth of code. This is never a good idea. Whichever function is reacting to the mousemove event, it must be short and to the point. Do whatever you can do to slim it down: Declare variables ahead of time, create element references before the function instead of within it, and try to avoid function calls and take action from within the event handler -- these would be some steps you could take.
Don't get me wrong, computers these days can take the heat, but consider that whatever you're developing may be viewed by a mobile device with a lot less computing power than a desktop or a laptop, it may be a handheld device for which it's hard enough just to render a semi-complex CSS-based design. Consider who you're targeting with your program (and what potential devices), and always assume that the computing resources that they have at their disposal are a lot less than your own.

Now that the lecture's over with, let's get to some meat. The coordinates of the mouse pointer are located within the clientX and clientY properties of the Event Object, in both event models. The clientX and clientY properties will give you the distance of the mouse pointer, from the left of the browser's rendering box, and from the top of the browser's rendering box, respectively.

Here's a basic implementation:

function mouseTrack(objEvent){ if(!objEvent){objEvent = window.event;} document.forms.mainform.field_one.value = objEvent.clientX + ", " + objEvent.clientY; }



Move your mouse pointer within the square above. Do you see the problem yet? The "problem" was actually defined quite clearly: "The clientX and clientY properties will give you the distance of the mouse pointer, from the left of the browser's rendering box, and from the top of the browser's rendering box, respectively". No one said boo about scrolling. Is the page scrolled 2000 pixels down? Does that mean that if you wanted to, say, have something follow the mouse pointer, this would be completely useless? Well nobody talked about implementation, these are just methods providing you with what the specs told them to provide you with.
Well, it would have been nice to have properties which returned the distance from the top and left of the document, but there aren't any that do so directly, so we have to make them ourselves.

This is one of those times when, oddly enough, Internet Explorer and the rest of the browsers around don't agree with one another. But wait, there's more: Internet Explorer 6 may actually disagree with -- wait for it -- itself. Yes, that's right: The problem here isn't between two separate versions of Internet Explorer, but rather one version of Internet Explorer, in this case, the much beloved version 6, that may actually disagree with its own self-defined functions.
I do not wish to cause you any further grief, and this doesn't really have to do with anything other than old browser history, so I'll just give you the solution:

var HS; var VS; function updateScroll(){ if (window.innerWidth) { // All browsers but Internet Explorer HS = window.pageXOffset; VS = window.pageYOffset; } else if (document.documentElement && document.documentElement.clientWidth) { // These are for Internet Explorer 6 when a DOCTYPE is defined HS = document.documentElement.scrollLeft; VS = document.documentElement.scrollTop; } else if (document.body.clientWidth) { // These are for Internet Explorer 5/6 without a DOCTYPE HS = document.body.scrollLeft; VS = document.body.scrollTop; } }

There's not much to add here. Whenever you want to get the horizontal scroll and/or the vertical scroll of the page, call updateScroll(). It will place the horizontal scroll value and the vertical scroll value into HS and VS, respectively.

At long last, we can implement a useful mouse-catcher:

function mouseTrack(objEvent){ updateScroll(); if(!objEvent){objEvent = window.event;} document.forms.mainform.field_one.value = (objEvent.clientX + HS) + ", " + (objEvent.clientY + VS); }

And in action:



I'll have to leave it up to you to write the script that has a spider run after the mouse cursor. Personally, I'm partial to the bee that flies around the cursor because it's dripping honey, but to each their own.

There's a lot more you can do with events, this is just the foundation, and it's not even the complete foundation: you can capture key strokes, scroll events, and there's a whole slew of events designed specifically for handling forms. But there's plenty here to get you building all sorts of incredible machines, especially if you combine this with DOM Scripting -- that's a deadly combination.





Return to the JavaScript section, or go the to Main page.





Looking for the old guiStuff?

It's still here, the old content didn't go anywhere.