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?
- 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.
- 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'
. - 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:
- Its first argument is always the event object (on all browsers, including Internet Explorer).
- (New as of 1.6.0.) The event object is extended with methods like
stop
, so you can writeevent.stop()
rather thanEvent.stop(event)
. - (New as of 1.6.0.) Within the handler,
this
is a reference to the extended element the handler is associated with. - (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 callelement()
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.