observe

Event.observe(element, eventName, handler)

Registers an event handler on a DOM element.

Prior to Prototype 1.6, Event.observe supported a fourth argument (useCapture), a boolean that indicated whether to use the browser’s capturing phase or its bubbling phase. Since Internet Explorer does not support the capturing phase, we removed this argument from 1.6, lest it give users the false impression that they can use the capturing phase in all browsers.

An important note

First, if you're coming from a background where you'd use HTML event attributes (e.g. <body onload="return myFunction()">) or DOM Level-0 event properties (e.g. window.onload = myFunction;), you must shed those vile ways :-) and understand what observe does.

It does not replace existing handlers for that same element+event pair. It adds to the list of handlers for that pair. Using observe will never incapacitate earlier calls.

What are those arguments about?

  1. The DOM element you want to observe; as always in Prototype, this can be either an actual DOM reference, or the ID string for the element.
  2. The standardized event name, as per the DOM level supported by your browser (usually DOM Level 2 Events, see section 1.6 for event names and details). This can be as simple as 'click'.
  3. The handler function. This can be an anonymous function you create on-the-fly, a vanilla function, a bound event listener, it's up to you.

The handler function

Four important things about how your handler function is called:

  1. Its first argument is always the event object (on all browsers, including Internet Explorer).
  2. (New as of 1.6.0.) The event object is extended with methods like stop, so you can write event.stop() rather than Event.stop(event).
  3. (New as of 1.6.0.) Within the handler, this is a reference to the extended element the handler is associated with.
  4. (New as of 1.6.0.) The element on which the event actually occurred (the "target") is available from findElement(). (Prior to 1.6.0, you would call element() method, which still works.) Note that the target might be a child of the observed element rather than the observed element itself, because of bubbling.

Which element was that again?

So there's this, set to the element the handler is on, and the "target" from event.findElement(). What's the difference? Bubbling. Consider this HTML:

<p id='target'>This event stuff is <strong>cool</strong>.</p>

That defines a p element containing a strong element, both of which also contain text.

Suppose you hook a click handler to that paragraph:

Event.observe('target', 'click', function(event) {
    // ...
});

Within your handler, this is always the p element. event.findElement(), though, will be either the p element itself, or (if you click the word "cool") the strong element within it.

There's more to Event#findElement() than just returning the element where the event occurred; it can also accept a selector and find the first ancestor matching that selector -- very handy for event delegation.

The requirement people too often forget…

To register a function as an event handler, the DOM element that you want to observe must already exist in the DOM (in other words, it must have appeared in the source, or been dynamically created and inserted, before your handler-registration script line runs).

A simple example

Let us assume the following (X)HTML fragment:

<form id="signinForm" method="post" action="/auth/signin">
…
</form>

Here's how to register your function checkForm on form submission:

Event.observe('signinForm', 'submit', checkForm);

Of course, you'd want this line of code to run once the form exists in the DOM; but putting inline scripts in the document is pretty obtrusive, so instead we'll go for a simple approach that waits till the page is fully loaded:

Event.observe(window, 'load', function() {
  Event.observe('signinForm', 'submit', checkForm);
});

Just a little wrapping…

Note that if your page is heavy, you might want to run this code before the page is fully loaded: just wait until the DOM is loaded, that will be enough. (Prototype's dom:loaded event can help you with that.)

The tricky case of methods that need this

Having this reference the observed element is handy, but sometimes you have other things you want to do with it. Perhaps your handler is a method on an instance of a class, and you want this within the method to refer to the instance. When you pass a function around (for instance, passing it into observe as the handler), just the function, not its context, is passed around -- it loses its binding. Later when it's called, this can mean something completely different from what you're expecting. Consider:

var IntMinMaxValidator = Class.create({

    initialize: function(fieldid, allowBlank, minValue, maxValue) {
        this.fieldid = fieldid;
        this.allowBlank = allowBlank;
        this.minValue = minValue;
        this.maxValue = maxValue;
        $(fieldid).observe('change', this.validateOnChange); // == WRONG
    },

    validateOnChange: function(event) {
        var value;
        value = $(this.fieldid).getValue();
        if (value == '') {
            if (!this.allowBlanks) {
                // ...report invalid blank...
            }
        } else {
            value = parseInt(value);
            if (isNaN(value) || value  this.minValue || value > this.maxValue) {
                // ...report bad value...
            }
        }
    }
});

When validateOnChange gets called, this will be the field element, not the instance! So this.minValue, etc., won't exist. Now you're in trouble.

Or not.

Prototype's bind function to the rescue. Using bind(), you can make sure your method gets the right this. Just change the indicated line above to this:

$(fieldid).observe('change', this.validateOnChange.bind(this)); // == RIGHT

See the bind documentation for details. See also bindAsEventListener, with which you can pass arguments to your event handler without using a closure.