Wednesday, July 11, 2007

Handling multiple events in Javascript

This is perhaps going to be my first initiative to write a technical post, the effort mainly driven by my long hours of pondering over a problem that I faced earlier today. It was then that I decided that it is definitely not worth having anybody spend their valuable time trying to figure this out by themselves.I know that my usual reader's would find this a bit out of place, but I hope that they wouldn't mind this....

Events are one of the most frequently used features in the world of Javascript. They provide you with the ability to capture many user events that can help in designing more intuitive and user friendly interaction with any application. Some of the events that we might want to capture could be keystorkes, mouse clicks, mouse movements, etc.. All that we need to do to achieve this is to register the event-handler with the browser. On occurance of the event, the browser will call the corresponding event handler. This event handler can contain any logic built into it depending on the event that triggered it. Okay.. that should give you the sufficient overview of what you can achieve by using events. So lets get going with an example.

The most common and traditional method of attaching events with elements in a web page is as shown below.

elt.onClick = handler();
elt.onFocus = handler();

whereas the newer methods of using events and handlers are as shown below

document.addEventListener('',event_handler,false);
document.removeEventListener('',event_handler,false);

function_handler()
{
//do something here....
}

where event could be a 'click', 'keypress'

The race for winning the web-war between the two giants Netscape and Microsoft has had its impact on their way of handling events too. The above method is the one used by Netscape to register events. Whereas Microsoft uses the format shown below.

element1.attachEvent('onClick',event_handler1)
element2.attachEvent('onClick',event_handler2)


To detach an listener is equally easy..,

element1.detachEvent('onClick',event_handler2)

If you are unsure of the browser then you might want to use something like this..

function addEvent (elt, eventType, handler, captureMode){
if(docuement.addEventListener){
elt.addEventListener(eventType,handler,captureMode)
}
else{
elt.attachEvent('on'+eventType,handler);
}
}

Okay.. If you had observed the above example carefully, you might be wondering how this would work with multiple elements being registered for the same event. That's where the borwsers are smart. They keep track of the various handlers registered for that particular event, and they notify them all. But hey.., hold on, it ain't so simple.. you never know in which order the events are notified to the handlers. IN earlier versions it was not even possible to determine the order in which the handlers will get triggered. This can cause a lot of confusion when we have multiple handlers with the same target.

Now that we are familiar with the basics of handling events let get into the intricacies. Say for example that we have two elements e1 and e2 where e2 is deeper in the hierarchy. Both these are registered for the same event say 'keyDown'. Now when the event transpires, what order do the listeners follow in calling the corresponding handlers. Again, the titans opted for varying solutions and w3c ended up on a median. Nestcape said that the event should proceed from e1 to e2 (called as event Capturing), whereas Microsoft went for the opposite. Event proceeds from e2 to e1 which is called as 'Bubbling'. This is what we specified as the fourth parameter 'captureMode' in our addEvent() function earlier. Now w3c had to take a middle course by adoption both these models. For this purpose, we have a capturing phase and a bubbling phase.

e1.addEventListener('keyDown',handler1,false)
e2.addEventListener('keyDown',handler2,false)


where 'false' indicates a bubbling mode.

When the event on e2 is triggered, it starts off in the capturing mode where in it checks if the ancestor has any listeners in the capturing mode. Finding none, it will proceed to call handler2 and the bubbling mode starts off then. During the bubbling mode it will accordingly call the handler1 and traverses further till the top of the DOM is reached. Now consider the other case,

e1.addEventListener('keyDown',handler1,true)
e2.addEventListener('keyDown',handler2,false)


As usual it begins in the capturing mode on e2, and finding that the ancestor of e2 ie., e1 is in the capturing mode executes the handler1 before moving on to the handler2. Now if there were an element e0 which is the ancestor of e1 in the bubbling mode with handler0 then hanler0() would be the last to be called in the bubbling phase. Thus providing a feature to attach each listener with their own mode, it provides micro-control the order in which handlers should be called.

Now the next natural question is can we stop this bubbling ? The answer is yes..imagine a scenario where e2's handler should determine whether e1 should receive the event at all. We can stop the event from bubbling by event.cancelBubble = true which would prevent the next handler in the hierarchy from receiving the event. This could prove to be useful feature when the DOM is rich and you would want to stop the event from propagating up to the top of the hierarchy when there are no events attached to any of them. Please note that this is the method used by Microsoft. w3c suggests another implementation to do exactly the same

e1.stopPropagation();

so what do we do to ensure cross browser compatibility..? Its the plain old way of doing it..

function stopEvents(event){

if(event.stopPropagation){
event.stopPropagation();
}
else {
event.cancelBubble;
}
}


So are there any pitfalls here..? yes .. A couple of things that I can think of. Please do correct me if I am wrong.

There is no way of determining whether event handlers are registered to an element (in older implementations elt.onclick() would give you the handlers registered with it). So if we want to ensure that there are no other eventhandlers to an element we should probably use elt.removeEventListener(). Another issue that I faced was that the propagation in the capturing phase cannot be stopped. The only way to control could be in the bubbling phase, so one should be using the mode pretty carefully depending on the design. One other problem that I came across was something like this...

e1.addEventListener('click',handle1,true)
e2.addEventListener('click',handle1,true)


So now I had to determine what was the originator of the event. The solution was pretty simple, use the 'this' operator to determine the source. Recently I also realised that w3c had another variable dedicated for the purpose called 'currentTarget' which contains the reference of the element. But then I found this to work only with MOZ and not with IE. Perhaps IE doesnt have a 'currentTarget' implementation. However make a note that the 'srcElement' or 'target' would still be pointing to the initial element where the event originated.

And now if you might have any doubts, suggestions or clarifications, please post them as comments and I would be more than pleased to have a look at it.

Monday, July 09, 2007

Wimbledon '07.. clash of titans


"Yeah..." shouted Federer when he won a net point. That's something the media tapes should preserve like a treasure, for one doesn't hear those words coming out of Federer very often. Wimbledon 2007 has been a dull event so far with just a few sparks flying in the Federer-Fererro clash. Federer was definitely the one expected to win the title hands down. He did that but definitely not hands down as people would have imagined it. Things looked ominous when the match started and Federer raced to a 3-0 lead within 11 minutes of the start of the game. Nadal had barely warmed up when Federer was looking threateningly dangerous to breeze away with the first set. But Rafa was not the one to bow down so early... He ploughed his way back into the game by winning the next three games to bring down the early advantage that Federer held. The first set was an absolute treatise to watch and every one who watched the match had their pennies' worth for it. Rafa matched Federer shot by shot, point by point. The tie breaker saw Federer having three set points but Rafa prised out a couple of blitzkreigs to extend the game before losing it 9-7.



Take nothing away from Federer, for he played at his very best to bring out an amazing array of aces (totaled to 24 if I am not wrong) and kept Nadal lurching for points. Nadal is definitely not a great net player but it amazed me to see Federer miss his net volleys too.. Both played excellent base game but were highly error prone when it came to net points. Federer's smooth and stylish backhands were suitably returned by Nadal's curvy forehand top spins and there were many a great rallies played in this fashion. Both played to their strengths and one could see glimpses of an epic match in the making. The king of grass faltered just one time in the 9th game of the second set which was all that Rafa needed to seal the second set 6-4. This was the seventh set that Federer lost in the 53 matches and 5 years of this tournament !

The genius in Federer was matched by the resilience of Rafa and each held serves dragging the third set into yet another tie break. Federer's aces helped him seal the set quite easily at 7-3. Things looked dark for Rafa but the inspired Spaniard took the challenge by its horns and pounded the King of grass in the Fourth set to a whopping 4-0 lead. This was the first time that one has seen Federer run to every corner of the court. Nadal stamped his brilliance by playing to his strengths, his forehand shops and top spins were impeccable and amazingly accurate. Fourth set was quite a contrast to the first three, Federer looked lost and even cursed a couple of times... it was Nadal all the way.. Rafa whoppled the set 6-2 bringing the game to a 5 setter epic.

Federer had not broken Nadal for eternity (save the second game of the match) while the spaniard had comfortably broken him more than thrice. The first three games saw them hold their serves and fight hard to earn every point. Then the world saw why Federer was called the 'King of grass'. He broke Nadal twice in the set while the spaniard failed to do the same when he had his chances. Federer would have had a run for his money if only Rafa had to manage to clinch at least one of the breaks. You cant expect Federer to be too benevolent to give a second chance ;-) he ruthlessly murdered the Nadal challenge in the final set to clinch the tile 6-2. The Swiss exulted with a rare outburst of emotion.. it was quite understandable....


Whatever be the result of the match, one thing is pretty evident.. these two are the rulers of modern day tennis be it grass or clay... They have simply discarded the others from either versions of the game. Federer said after the match that the title could have easily been Rafa's too. He wasnt made to prespire as much in any version of the game ever before. Such was the quality and strength of Nadal. It was a fitting end to the rain-hit wimbledon where two legends fought with valor for the title in the presence of all time greats like Becker and Borg. With the fifth straight wimbledon title safely tucked under his belt, can the King of grass rest easy..?? Well hardly, with the way he was made to run for the title this time he can hardly afford to put away the Spaniard's brilliance. Nadal's performance in this final had thrown in many questions to the tennis world... is this the end of the Federer era...? Well, time will tell....