Refactoring your code into the event delegation pattern in Prototype
Standard event pattern
In this example we attach onclick behaviors for the .more
anchor tags. When these tags are clicked a certain part of the paragraph will become highlighted. The function highlightMoreTexts
is the registered event handler for the onclick behavior. Inside of highlightMoreTexts
the this
variable references the element that is clicked on. That is because observe
will register event handlers with this
bound to the currentTarget property.
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Refactor to event delegation</title> <script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js" type="text/javascript"></script> </head> <body> <div class="row"> <p>Incrementing nonalphanumerics <a href="#" class="more"><span>highlight..</span></a><span class="moreText">uses the underlying character set's collating sequence</span>. </p> </div> <div class="row"> <p>Incrementing nonalphanumerics <a href="#" class="more"><span>highlight..</span></a><span class="moreText">uses the underlying character set's collating sequence</span>. </p> </div> <script> document.observe( 'dom:loaded',function(){ function highlightMoreTexts(event){ this.next('span').setStyle({ background: 'yellow' }); event.stop(); } $$('.more').invoke('observe','click',highlightMoreTexts); }); </script> </body> </html>
Put into a event delegation pattern and be done with it
Now let's take the first step in refactoring into a event delegation pattern. Below we will grab all of the clicks within the div.row
tags. Using event.findElement
we can figure out when a.more
was actually the element clicked within div.row
. Next, just stick in the highlightMoreTexts
function call with a manual passing of the event
object and we are done. Right?
$$('div.row').invoke('observe','click',function(event){ if(el = event.findElement('.more')){ highlightMoreTexts(event); } });
Not so fast
This first attempt will not work because the this
reference will be bound to the Window object. You may next decide to rewrite your handler to be more portable and take out the this
reference and use event.target
itself.
function highlightMoreTexts(event){ event.target.next('span').setStyle({ background: 'yellow' }); event.stop(); }
Still not done
Using event.target
will work fine in the standard event pattern as detailed in the first paragraph but will again fail in the event delegation pattern. This is because there is a <span>
tag inside of the <a>
tag and findElement
will match all events within the selector .more
. Therefore, event.target
inside of the highlightMoreTexts
handler will be the span
within the a.more
tag.
Last step
Here is what we want to do and we don't have to rewrite any of our original event handler functions. In the code below, the el
variable has the current extended element, meaning $(a.more)
. So, we take the el
variable, which again references $(a.more)
and bind it as the this
scope for the highlightMoreTexts
function. The next bind argument is the event
object. Remember the first argument in the bind
method on the function object will be the this
scope for that function. After the first argument the rest of the arguments for bind
will set arguments for the highlightMoreTexts
function. Read more on Function.bind()
.
/* * Use Function.bind() to refactor into the event delegation pattern * and you will not have to touch your existing event handlers */ $$('div.row').invoke('observe','click',function(event){ if(el = event.findElement('.more')){ var fn = highlightMoreTexts.bind(el,event); fn(); } });
- Pushed on 06/03/2010 by Christian