Switch On The Code RSS Button - Click to Subscribe
Aug
10

Javascript - Working With Events

Events are, arguably, the most important part of how javascript and the DOM work together. Without events, there would be no user interactions with web pages - they would just be static. Now, pretty much everyone who has written a web page knows how do do simple eventing in javascript (and if you don't, there is an example right below) - but what about more complex situations? Such as attaching more than one callback to an event, or removing a callback from an event? That, and some wrapper functions that allow us to do this in a cross-browser compatible way, are what we will be covering today.


The simplest way to attach to an event for an element is to write the javascript call straight in inline in html code - and this also happens to be the most compatible way. Often, the inline code just calls a function defined elsewhere. For example, here, when this div element is clicked, the javascript function myFavoriteFunction is executed.


<div onclick="myFavoriteFunction();"></div>


A less common use is to actually write multiple javascript statements directly into the html. Here, for whatever reason, we are executing two alert calls when the div is clicked.

<div onclick="alert('Message 1'); alert('Message 2');"></div>

The next most common way to attach events is to attach them directly to the element object in javascript. In the following code, we obtain an element object using getElementById, and then attach the myFavoriteFunction method to the onclick event by setting the onclick property of the element object:

var element = document.getElementById("myFavoriteElement");
element.onclick = myFavoriteFunction;

Note that the line is element.onclick = myFavoriteFunction, not element.onclick = myFavoriteFunction(). This is because we are setting onclick equal to the function itself, not the result of the function. If the parentheses are there, the function would be evaluated.

This technique can also be used to remove an event - by setting that property back to null:

var element = document.getElementById("myFavoriteElement");
element.onclick = null;

These methods of working with events are supported by every browser, but they leave a little bit to be desired. The major flaw: attaching multiple callback functions to an event. Fortunately, there are more advanced methods of attaching and detaching events. But like almost everything else in the world of html and javascript, the techniques differ slightly between browsers.

For most browsers following the W3C standards, the function addEventListener is used for attaching events, and the function removeEventListener is used to detach them. They work in the following manner:

var element = document.getElementById("myFavoriteElement");
element.addEventListener("click", myFavoriteFunction, false);
element.removeEventListener("click", myFavoriteFunction, false);

That seems pretty straightforward - except what does the last parameter to that function mean? That parameter is there because in the W3C standard there are two possible times that any event can be triggered. One is during the capture phase, and the other is during the bubble phase. The capture phase goes first - which is as an event travels downward through parent nodes to reach the final child destination node. The bubble phase is the opposite direction - as an event travels back upward from its destination node (again passing through all the parents). So, for instance, if you had an image in a table cell, which is in a table row, in a table, which is in on an html page, and you clicked on the image, the following would happen in the W3C event model. First, if anything is attached to the document onclick event for the capturing phase, it would fire. Then the table would be checked, and then the row, and then the cell, and finally the image itself. Anything attached to the onclick event for those elements, and set to go off in the capturing phase, will be fired. Then the event bubbles: the onclick for the image again would be checked, but this time for the bubbling phase. And then again with the table cell, and then the table row, the table itself, and finally the document. This gives you, as a developer, an extreme amount of flexibility as to when your event will fire.

And so how do you set during which phase you want your function to be called? When that third argument is true, the function is hooked for the capture phase, and if false, it is hooked for bubbling. Oh, and a useful thing to note is that all events set using the traditional techniques are set to fire during the bubbling phase.

But of course, Microsoft has its own set of standards. Which means that in Internet Explorer, there the functions to attach and remove events are different: attachEvent and detachEvent. And, similar to the W3C methods, they are used in the following way:

var element = document.getElementById("myFavoriteElement");
element.attachEvent("onclick", myFavoriteFunction);
element.detachEvent("onclick", myFavoriteFunction);

But what happened to that third argument? Well, in the Microsoft standard, events ever only bubble - there is no capturing phase. So there is no need for that third parameter. What this means as well is that writing a web page and relying on events firing during capture phase is impossible - because such a page will not work in Internet Explorer. Sad, really, because a lot of flexibility is squandered.

Now, we don't want to have to worry about these differences when we are writing javascript code - so it is generally best to hide these incompatibilities with a wrapper. And that is exactly what we have here:

function hookEvent(element, eventName, callback)
{
  if(typeof(element) == "string")
    element = document.getElementById(element);
  if(element == null)
    return;
  if(element.addEventListener)
    element.addEventListener(eventName, callback, false);
  else if(element.attachEvent)
    element.attachEvent("on" + eventName, callback);
}

function unhookEvent(element, eventName, callback)
{
  if(typeof(element) == "string")
    element = document.getElementById(element);
  if(element == null)
    return;
  if(element.removeEventListener)
    element.removeEventListener(eventName, callback, false);
  else if(element.detachEvent)
    element.detachEvent("on" + eventName, callback);
}

These two functions will attach and detach events regardless of the browser, and they come with an added bonus. They can take either an element object, or an element ID as input for the element argument. For instance, they could be used in the following fashion:

hookEvent("myFavoriteElement", "click", myFavoriteFunction);
var el = document.getElementById("myFavoriteElement");
unhookEvent(el, "click", myFavoriteFunction);

Sadly, that is not where the differences between the standards end when it comes to eventing. First off, how to get the event object (the object that contains information about a fired event) is different between browsers. In the W3C standard, the event object is actually passed as an argument to the function callback. To get this event object passed when inlining the callback in html it needs to be in the following form:

<div onclick="myFavoriteFunction(event);"></div>

That event argument lets javascript know to pass the event object to the function. For the other methods - setting the property on the element and or addEventListener/attachEvent, it is assumed that the function you are giving will take one argument, which will be the event.

But with Internet Explorer, the event is not passed as an argument (except if it is passed in the inline form like above). Otherwise, it is only accessible as a property of the window:

var event = window.event;

This is pretty easily wrapped, and heres an easy way to do it:

function getEvent(e)
{
  if(!e)
    return window.event
  return e;
}

And here is how you can use it:

function myFavoriteFunction(e)
{
  var e = getEvent(e);
  //do stuff with the event
}

hookEvent("myFavoriteElement", "click", myFavoriteFunction);

Of course, the event object by itself is not that useful - it is the contents that matter. For instance, you can get the target of the event from this object. But, yet again, the W3C and Microsoft models are different. For W3C it is the target property, but for Microsoft it is the srcElement. Once again, it is pretty simple to write quick wrapper:

function getEventTarget(e)
{
  if(!e)
    e = window.event;
  if(e.target)
    return e.target;
  return e.srcElement;
}

function myFavoriteFunction(e)
{
  var target = getEvent(e);
  //do stuff with the target
}

hookEvent("myFavoriteElement", "click", myFavoriteFunction);

Another really useful thing to be able to do is cancel the event. This means that the event will stop propagating - i.e., nothing else on the capture or bubble chain will fire. Sadly, there are a lot of different ways to do this across browsers. The main ones are the property cancelBubble for Internet Explorer (which you set to true if you want it to stop propagating) and the function stopPropagation for W3C compliant browsers. The function preventDefault will prevent default actions from happening in Firefox (like the Page Up key actually moving the page upward). The properties cancel and returnValue are listened to by various other browsers (and browser versions), and its just always good to return false out of an event function if you want to stop propagation. So heres a nice little wrapper that will do all that for you:

function cancelEvent(e)
{
  if(!e)
    e = window.event;
  if(e.stopPropagation)
    e.stopPropagation();
  if(e.preventDefault)
    e.preventDefault();
  e.cancelBubble = true;
  e.cancel = true;
  e.returnValue = false;
  return false;
}

function myFavoriteFunction(e)
{
  //do stuff
  return cancelEvent(e);
}

hookEvent("myFavoriteElement", "click", myFavoriteFunction);

And that is it for this tutorial on working with javascript events. There are a number of other things in that event object, such as mouse position and keys pressed - but that is a topic for another day. If you have any questions about how events are supposed to work, feel free to leave them in the comments.



Posted in Javascript, All Tutorials by The Tallest |

10 Responses

  1. Aamir Says:

    Wonderful article. I enjoyed it.

    Thanks
    Aamir

  2. Paul Mele Says:

    Thanks for the examples!

  3. Nilesh Says:

    very nice, informative article, I have a one query regarding Javascript Event,
    in one the condition I have one Span tag inside a TD tag of and i already registered onclick event of td. it work fine in firefox but in IE when event fires i get srcElement either Span or TD element depends on click position. but actually i always need TD element. do you have any solution for this.

  4. The Tallest Says:

    Yeah, this is a known problem in the Microsoft model. If you want to, you can read more about it here.

    The solution that I generally use in cases like yours is to check the tag name. If the tag of the srcElement is “SPAN”, look at the parent element of srcElement. Otherwise, just look at the srcElement.

  5. Jeto Says:

    Why would one want to cancel a event after putting it there. Can you please explain using a practical example?

  6. Farrukh Momin Says:

    superb article, i really enjoyed using it.
    excellent work

  7. Anonymous Says:

    all the tutorials are really niece and good.
    great job!

  8. Fernando Says:

    Thank you for this tutorial! It will help me a great deal. Very complete.
    I have just discovered this site, and I will return often.
    You have done a great job.
    Fernando

  9. Aseem Says:

    Terrific article — thanks so much for the comprehensive look!

  10. Mitch Says:

    Hello,

    Thanks for the article. I have one question, though. What if I want to attach a function that has parameters to an event? In all of the examples provided, the sample function being attached to the ‘onclick’ event does not have any parameters passed in.

    When I try use ‘myfunct(param1, param2)’, it seems like the function gets executed, which is not what I want to happen.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Powered by WP Hashcash