Saturday, December 24, 2005

Prototype.js: Event

If you write a lot of JavaScript code it is likely that you have written pleanty of event hendlers in your day. For simple tasks, this is usually failry straight forward to write. If you need to get data from the Event object, well, you are in for some pain. When Microsoft won the browser wars you could, for the most part, ignore other browsers. Firefox currently has about 10% of the market, and Safari has about 5%, neither of which can be ignored.

In this installment of the Prototype series I cover the Event API. It only has a few methods, but these methods are built to be cross-platform compatible. It also includes a few extensions that are rather useful for some types of work.

API Constants

The Event object includes the following constants which can be used for the handling of keypress events so that you don't need to remember the key code for these keys.


Event.KEY_BACKSPACE
Event.KEY_TAB
Event.KEY_RETURN
Event.KEY_ESC
Event.KEY_LEFT
Event.KEY_UP
Event.KEY_RIGHT
Event.KEY_DOWN
Event.KEY_DELETE


The Prototype Event object doesn't include any helpers for handling keypress events, but you could perform a check like this.


function keypressHandler (event)
{
// I think this next line of code is accurate,
// but I don't have a good selection of browsers
// with me today to test this effectivly.
var key = event.which || event.keyCode;

switch (key) {
case Event.KEY_RIGHT:
alert('moved right');
break;
case Event.KEY_LEFT:
alert('moved left');
break;
}
}


API Summary


Event.element(event)
Event.findElement(event, tagName)
Event.isLeftClick(event)
Event.observe(element, name, observer, useCapture)
Event.observers
Event.pointerX(event)
Event.pointerY(event)
Event.stop(event)
Event.stopObserving(element, name, observer,
useCapture)
Event.unloadCache()


API Details

Event.element(event)

Params:
event - An Event object

Returns:
The target element for the event.

This method, as well as all of the Event methods, are static methods. This means (for those not familiar OOP) that you call the method on the Event class, and not an instance of an Event. The returned element would be the button that was clicked, the text field that changed, or whatever element triggered the event.

Event.findElement(event, tagName)

Params:
event - An Event object
tagName - The name of a tag (e.g. "div", "a", etc.)

Returns:
An HTML element.

This is a cool little helper function that could potentially be very useful in certain cases. It takes an event and a tag name, and returns an HTML element of that type that is closest to the target event (see Event.element(event)). The tag name you pass is used in a case-insensitive manner.

The method determines the result by starting at the target element and traversing the HTML DOM backwards, meaning through the parent nodes until the document root is reached. If the tag is not found, the document root is returned. An easy way to test a failure to find an element of the specified tag name is to check for a value in the tageName property of the returned node.


function doStuff (event)
{
var node = Event.findElement(event, 'div');

if (node.tagName) {
// we found a tag
}
else {
// there was no tag by that name found
}
}


Event.isLeftClick(event)

Params:
event - An Event object

Returns:
Boolean value, true if the left mouse button was clicked.

With this method the rule is buyer beware. The method SHOULD return true if the left mouse button is clicked, but this may not always work as expected. For example, on my Dell laptop clicking a button in Firefox will return true for left click, while in Internext Explorer it will not. The Prototype library does it's best to accuratly return the correct value, but as with all JavaScript projects, be sure to cross-browser test your work.

Event.observe(element, name, observer, useCapture)

Params:
element - An element object, or element ID
name - the event name to observe
observer - A function reference
useCapture - see Gecko DOM reference

Returns:
nothing.

This is the most important method in the Event class, allowing you to set event handlers in a browser agnostic fashion. On some browsers (notably IE) the event object is NOT passed to the event handler, which makes it a pain to code handlers in a cross-browser compatible way. This solves that issue, and will guarantee that the event object will be passed to the hendler on all browsers.

Note that you can not attach an event to an element until after the page has loaded. So you should call this method only when you are sure that the page load is complete, as in the example below. Also notice that the event name is the name of the event without the familiar "on" prefix. For example, use "click", not "onclick".


window.onload = function ()
{
// handle onclick event for element of ID="foo"
Event.observe('foo', 'click', doStuff);

// handle onchange event for element of ID="bar"
Event.observe('bar', 'change', doStuff2);
}


Event.observers

Returns:
false if no observers, otherwise an Array

This property will return a list of Array references, one for each event being observed, or false when no events are being observed. The Array referece contains 4 elements, the same as the arguments passed to the Event.observe() method, namly [element, name, observer, useCapture]. This is useful is you ever have a reason to inspect the events currently being handled.

Event.pointerX(event)

Params:
event - An Event object

Returns:
The x-coordinate of the mouse

Returns the x-coordinate of the mouse at the time the event is triggered, even if the mouse did not trigger the event. This is done in a cross-browser compatible way.

Event.pointerY(event)

Params:
event - An Event object

Returns:
The y-coordinate of the mouse

Returns the y-coordinate of the mouse at the time the event is triggered, even if the mouse did not trigger the event. This is done in a cross-browser compatible way.

Event.stop(event)

Params:
event - An Event object

Returns:
nothing.

This method attempts to stop the propogation of the event in a cross-browser compatible way. Again, milage may vary, and cross-browser testing is essential.

Event.stopObserving(element, name, observer, useCapture)

Params:
element - An element object, or element ID
name - the event name to observe
observer - A function reference
useCapture - see Gecko DOM reference

Returns:
nothing.

This method removes an event handler that was set using Event.observe(). To get a list of observed events as runtime, see the Event.observers property.

Event.unloadCache()

Params:
event - An Event object

Returns:
nothing.

This method removes all event handlers registered using Event.observe(). This method is automatically called on page unload so as to prevent memory leaks.

7 comments:

Andrew Dupont said...

Robert, great writeup on Prototype events. One minor correction: "Event.observe" does not fix the "window.event" issue in IE automatically. Using the "bindAsEventListener" method does, however. So if you're writing ordinary procedural JavaScript, defining functions in the "window" scope, then you'll still have to check for window.event, since you won't need to use bindAsEventListener. Hope that made some sense.

Manu said...

Dear Robert,
Please add some examples for some key combinations like shift+down arrow. These type of combinations are explained in http://www.webmonkey.com/webmonkey/05/
10/index4a_page9.html?tw=programming

ianaz said...

Ed un esempio? :)

CrackWilding said...

Looks like Event.observers has been removed in a subsequent version of Prototype. Anyone know how to get a list of event handlers now?

Anonymous said...

How to observe on text fields, I am unable to do that. I am able to observe on Input fields. I am talking about pages created in Peoplesoft application.

Robert Hanson said...

I assume you mean a text area as apposed to a text box. It has been a long time since I did this research, and the online prototype docs have gotten a lot better. I suggest you start there (i.e. I don't know) http://www.prototypejs.org/api.

sexy said...
This comment has been removed by a blog administrator.