Articles: 843 | Categories: 148   
   
   
Home Articles Contact Us
 
 
 
 
Event Handling in Prototype (0 Comments)
Admin: Posted Date: April 4, 2010

One of the most useful and important aspects of JavaScript developments is that of event handling. Prototype simplifies this process by providing a number of helpful methods for doing so.

Event Handling in Prototype

Introduction

One of the most useful and important aspects of JavaScript developments is that of event handling. Prototype simplifies this process by providing a number of helpful methods for doing so. In this, the fourth article of "Eight of Weeks of Prototype", we will look at how Prototype helps with handling events.

In addition to the native events (such as onclick or onmouseover), I will also show you how to observe and handle custom events, which can help you dramatically when developing your Prototype-based JavaScript.

Aside from the useful helper methods provided by Prototype, there is another excellent reason for using Prototype to handle the events. By using Prototype, you can ensure that you don't write over existing event handlers.

To demonstrate this, look at the code in Listing 1. Based on this code, do you know which message will appear? This situation can occur when you use third-party libraries over which you have no control.

Listing 1 Demonstrating event handling ambiguity (listing-1.html)
<html>
<head>
<title>Demonstrating event handling ambiguity</title>
<script type="text/javascript">
window.onload = function() {
alert('Message 1');
}
</script>
</head>
<body onload="alert('Message 2')">
</body>
</html>

When using the observe() method provided by Prototype (which we will cover shortly), this problem does not arise, since Prototype will automatically append the new event handler to the existing handlers.

Observing Events

In order to use Prototype to handle events, the key method to use is observe().This method is one of the element extensions added by Prototype (as we saw in part 2 of this series). This means you can call observe() directly on the element on which you wish to observe an event, or you can pass that element as the first argument to Event.observe().

Listing 2 demonstrates the two different ways of observing an event on the same element. Note that in both cases we are using the $() function which you should now be familiar with (covered in the first article of this series). I've included some basic event handlers in this example: try hovering your mouse over the text or clicking on the text.

Listing 2 Two different ways of observing an event on an element (listing-2.html)
<html>
<head>
<title>Two different ways of observing an event on an element</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="foo">
This is the element to observe
</div>
<script type="text/javascript">
var elt = $('foo');
Event.observe(elt, 'mouseover', function() {
$('foo').setStyle({ backgroundColor : '#fc0' });
});
elt.observe('click', function() {
alert('Click 2');
});
elt.observe('mouseout', function() {
$('foo').setStyle({ backgroundColor : '' });
});
</script>
</body>
</html>

The most important concept to take from this example is that you can use either Event.observe(elt, ...) or elt.observe(...). In order to observe events on the page as a whole (that is, with the window object), you must use Event.observe().

This is demonstrated in Listing 3, where an alert box is shown once the page completes its loading. This code is the "correct" way to do what is done in Listing 1. That is, rather than using <body onload="..."> or window.onload, we use Prototype's observe() method.

Listing 3 Checking for page load completion (listing-3.html)
<html>
<head>
<title>Checking for page load completion</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<script type="text/javascript">
Event.observe(window, 'load', function() {
alert('Page has loaded!');
});
</script>
</body>
</html>

Now that you know the two different ways of invoking observe(), let's look at the other arguments passed to this method. Firstly, the name of the event is passed. You may have noticed from listings 3 and 4 that on is omitted from the event name. That is, if you wanted to observe the onclick event you would pass click as the argument. Technically click is what happened (the event), and onclick is what happens when the click occurs.

The final argument passed to observe() is the function you want to execute when the event is triggered. We will look more in depth at how to write these functions in the next section.

As usual, you can pass either a function pointer, or define the function there and then. Listing 4 shows you these different ways of defining the event handler function.

Listing 4 Two different ways of defining event handlers (listing-4.html)
<html>
<head>
<title>Two different ways of defining event handlers</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="foo">
Click me!
</div>
<script type="text/javascript">
// method 1: defining the function inline
Event.observe(window, 'load', function() {
alert('Page has loaded!');
});
// method 2: defining the function then passing its name to observe()
function handleFooClick()
{
alert('I was clicked!');
}
$('foo').observe('click', handleFooClick);
</script>
</body>
</html>

In general (and this is typically true for all JavaScript code you write), it's better to define the function separately from the call to observe(). This is because your code becomes much easier to maintain when done in this way. You will especially see how this helps in the sixth article of this series (when I show you how to write classes with Prototype).

In some rare cases I will define an event handler inline if it is performing some extremely trivial functionality.

Handling Events

I will now show you how to write event handlers when observing events with Prototype. I showed you some basic examples in the previous section of handling some events, but there is more to it than that!

When you define an event handler, the event object is passed as the first argument to the handler. This allows you to find out more information about the event, such as which specific element the event occurred on or to determine which key was pressed (for a key-related event).

Listing 5 shows a basic example of how to access this object. My own personal preference for the name of the event object is to use e, since if you use event you may confuse this with the JavaScript Event object.

Typically this variable will be used in conjunction with the Event variable. That is, rather than methods being available to call directly on e, they are passed to the relevant Event method (such as Event.element(e)).

Listing 5 Demonstrating how the event object is passed to handlers (listing-5.html)
<html>
<head>
<title>Demonstrating how the event object is passed to handlers</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="foo">
Click me!
</div>
<script type="text/javascript">
function handleFooClick(e)
{
// you can now do something with e,
// such as call Event.element(e)
}
$('foo').observe('click', handleFooClick);
</script>
</body>
</html>

Stopping an Event

Frequently you will want to stop an event from completing, since you are defining a method by which to handle the event, and therefore don't want the browser to use its own handling method.

The two best examples of this are for hyperlinks and forms. Firstly, let's look at links. Often you will want to perform some action when the user clicks a link, however you don't want the browser to follow the link.

Note: This is especially useful if you want to use Ajax to retrieve the page at the given link. This specific example will be shown in the fifth article of this series.

Before using Prototype, you might be more familiar with returning false from the event handler. For example, you might use <a href="..." onclick="doSomething(); return false">...</a>. By returning false in the onclick handler, the browser knows not to follow the link in the href attribute.

In order to achieve this same effect with Prototype, the Event.stop() method is used instead. Returning false from your handler method will have no effect. Calling Event.stop() tells Prototype to stop event propagation and not perform the default browser action.

Note: If you have multiple handlers for a given event, all of them will be executed regardless of whether you have called Event.stop() in any or all the handlers. I'll cover this in more detail shortly.

Listing 6 shows an example of stopping an event if required to do so. In this example, a JavaScript confirmation box is displayed to the user. If they click OK, then the link is followed, whereas clicking Cancel will result in the link not being followed.

Note: Although not specifically related to this concept, you should typically use a POST form if your action has some side-effect on your application (such as deleting data from a database) rather than a normal hyperlink.
Listing 6 Stopping an event with Event.stop() (listing-6.html)
<html>
<head>
<title>Stopping an event with Event.stop()</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<a href="/path/to/do/something.php" id="myLink">Do Something!</a>
</div>
<script type="text/javascript">
function onMyLinkClick(e)
{
var msg = 'Are you sure you want to do this?';
if (confirm(msg)) {
// user click ok, nothing to do - link will be followed as normal
}
else {
// user clicked cancel, stop the event
Event.stop(e);
// link will now not be followed
}
}
$('myLink').observe('click', onMyLinkClick);
</script>
</body>
</html>

This same concept can be useful implementing JavaScript-based form validation. By observing the submit event, you can then check form values before deciding whether or not to allow the browser to submit the form.

Listing 7 shows an example of how this is achieved. In this example, I've created a form with a single text input. The validation routine (onFormSubmit()) checks if this text input is blank (we covered the blank() method in the third article of this series), and if so records an error.

To complete the event handler, we check if there are any errors (by checking the size of the errors array), and if so we stop the event from propagating and display an error message.

Note: The serialize() method is a special method for forms that allows you to easily retrieve all of the values in a single object. If true is not passed as the first argument then the data is returned as a "get" style string (which is difficult for us to validate).
Listing 7 Validating a form before deciding whether it should be submitted (listing-7.html)
<html>
<head>
<title>Validating a form before deciding whether it should be submitted</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<form method="post" action="/some/form/target.php" id="theForm">
<div>
<input type="text" name="name" />
<input type="submit" />
</div>
</form>
</div>
<script type="text/javascript">
function onFormSubmit(e)
{
// retrieve all values to check
var values = $('theForm').serialize(true);
// placeholder array for errors
var errors = [];
// check if the name was entered
if (values.name.blank()) {
errors.push('Please enter the name');
}
// check if any errors were found
if (errors.size() > 0) {
// display the errors
alert('There were errors:\n' + errors.join('\n'));
// prevent the form from being submitted
Event.stop(e);
}
}
$('theForm').observe('submit', onFormSubmit);
</script>
</body>
</html>

Note: Even though form validation using JavaScript is very useful to the user, you should not be relying solely on this validation in your application. You still need to validate all values on the server-side, since it is trivial for a user to bypass the JavaScript validation.

Technically you could observe the click event on the submit button rather than the submit event on the form, however the form might be submitted using a method other than this button. For instance, if the user presses enter while filling out the text input the form will be submitted (triggering the submit event but not the button click event).

Checking for Stopped Events

As mentioned in the previous section, if you have multiple event handlers, each of them will still be executed even if you called Event.stop() at any time. You can, however, check to see if an earlier handler has already stopped the event by reading the stopped property.

Listing 8 shows an example of doing this. In it, I have defined two different handlers for the click event, each of which will display an alert box. However, the alert box will only be shown if the event hasn't already been stopped.

Listing 8 Checking for stopped events (listing-8.html)
<html>
<head>
<title>Checking for stopped events</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="foo">
Click here
</div>
<script type="text/javascript">
function handler1(e)
{
if (!e.stopped) {
alert('Handler 1');
Event.stop(e);
}
}
function handler2(e)
{
if (!e.stopped) {
alert('Handler 2');
Event.stop(e);
}
}
$('foo').observe('click', handler1);
$('foo').observe('click', handler2);
</script>
</body>
</html>

Finding the Element on Which an Event Was Observed

In all of the event handlers in the examples so far, we have only observed events on a single element. In the event handler we have then used the $() function to select that element again to perform some function on it.

Using the Event.element() function, we can determine exactly which element and event was triggered on. This is useful when an event handler might be used for a particular event that may occur on several items.

Listing 9 shows such an example. In this listing, we use the $$() function select all of the list items so the click event can be observed on each of them. Using the each() method (covered in the third article of this series), we loop over each item and observe the event.

When the event is handled (that is, the onItemClick() method is called), we use Event.element(e) to determine the specific list item that the event was triggered for. We then update its background colour and change its text.

Listing 9 Retrieving an event's element with Event.element() (listing-9.html)
<html>
<head>
<title>Retrieving an event's element with Event.element()</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<ul id="foo">
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
</ul>
</div>
<script type="text/javascript">
function onItemClick(e)
{
var item = Event.element(e);
item.setStyle({ backgroundColor : '#fc0' });
item.update('Clicked!');
}
$$('#foo li').each(function(item) {
item.observe('click', onItemClick);
});
</script>
</body>
</html>

Sometimes you may not want the specific element on which the event occurred, but rather one of its parent elements. If you look at listing 9, how would we go about retrieving the parent <ul> element rather than the <li> that was clicked?

To do so, Prototype provides the method Event.findElement(). This is similar to Event.element(), except you specify a tag name as the second argument. That, you would use Event.findElement('e', 'ul') to find the parent <ul> element. This is shown in Listing 10.

Listing 10 Finding an event element's ancestor with Event.findElement() (listing-10.html)
<html>
<head>
<title>Finding an event element's ancestor with findElement()</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<ul id="foo">
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
</ul>
</div>
<script type="text/javascript">
function onItemClick(e)
{
var list = Event.findElement(e, 'ul');
list.setStyle({ border : '2px solid #fc0'});
}
$$('#foo li').each(function(item) {
item.observe('click', onItemClick);
});
</script>
</body>
</html>

The only problem with Event.findElement() though is that you can only find ancestor elements based on their tag name. If you wanted a more complex search (such as finding an ancestor with a particular class name), then you would want to combine Event.element() with the up() method (we covered up() in the first article of this series).

Listing 11 shows an example of doing this. In this example we find the div with the class name of outer. If we tried to use Event.findElement(), we would not have been able to retrieve this element since if we passed div as the second argument, the div with class inner would have been returned.

Listing 11 Combining Event.element() with up() (listing-11.html)
<html>
<head>
<title>Combining Event.element() with up()</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div class="outer">
<div class="inner">
<ul id="foo">
<li>Click Me</li>
<li>Click Me</li>
<li>Click Me</li>
</ul>
</div>
</div>
<script type="text/javascript">
function onItemClick(e)
{
var item = Event.element(e);
var outer = item.up('div.outer');
outer.setStyle({ backgroundColor : '#cf9' });
}
$$('#foo li').each(function(item) {
item.observe('click', onItemClick);
});
</script>
</body>
</html>

Handling Keyboard Events

So far we haven't concerned ourselves with any specifics of an event that occurs, other than on which element the event occurred. Often an event will be triggered by a particular key press or by some movement or action with the mouse. In this section we will look at how to handle events that are triggered by the keyboard.

To determine which key was pressed, you can read the keyCode property of the event object passed to your event handler. Prototype defines a number of useful constants that help you determine which key was pressed. Specifically, these are:

  • 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
  • Event.KEY_HOME
  • Event.KEY_END
  • Event.KEY_PAGEUP
  • Event.KEY_PAGEDOWN
  • Event.KEY_INSERT

To demonstrate how these codes can be used, I have written a simple example, shown in Listing 12. In this example there is a text input. If you hit the escape key the text in the input is selected, while hitting backspace results in the entire value being cleared. While this doesn't serve much practical use, hopefully it demonstrates how you can read the key codes.

Listing 12 Determining which key was pressed (listing-12.html)
<html>
<head>
<title>Determining which key was pressed</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="text" id="foo" />
</div>
<script type="text/javascript">
function onFooKeyup(e)
{
var element = Event.element(e);
switch (e.keyCode) {
case Event.KEY_ESC:
element.activate();
Event.stop(e);
break;
case Event.KEY_BACKSPACE:
element.value = '';
Event.stop(e);
break;
}
}
$('foo').observe('keyup', onFooKeyup);
</script>
</body>
</html>

Handling Mouse Events

It is possible to return the coordinates of the mouse for a particular event using the Event.pointerX() and Event.pointerY() functions. You simply pass in the event object passed to the event handler as the first and only argument to receive the integer value representing the location of the mouse.

These methods return values relative to the entire page, not just what is currently visible to you. In other words, if you've scrolled down on the page the returned values are still relative to the very top of the page.

Listing 13 shows an example of tracking the mouse movement within a particular element using the mousemove event. This event is triggered every time the mouse is moved whilst over this element. In the handler function we read the X and Y position of the mouse and update the element to display this information.

Listing 13 Reading the X and Y coordinates of the mouse (listing-13.html)
<html>
<head>
<title>Reading the X and Y coordinates of the mouse</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="foo">
Move the mouse over me
</div>
<script type="text/javascript">
function onMouseMove(e)
{
var element = Event.element(e);
element.update(Event.pointerX(e) + 'x' + Event.pointerY(e));
}
$('foo').observe('mousemove', onMouseMove);
</script>
</body
					  					  
					  
 
 
Add a Comment:
 
(You must be signed in to comment on an article. Not a member? Click here to register)
   
Title:

Comments: