Monday, November 28, 2011

This Hover Cruft is Full of Ills

Recently I spent time refactoring much of the event system in jQuery for version 1.7. Since we added some new event methods as well, I took another pass at the event-related documentation. It was an opportunity to rewrite the pages in a way that make more sense (at least, to me) and eliminate things that were important five years ago but not so much today. Just like code, documentation accumulates its own cruft over time. I also find that when things get ugly or inconsistent in the code, they often get ugly or inconsistent in the documentation as well.

Here's an example: In jQuery 1.4.2 we started allowing people to use the "hover" event name for the .live() and .delegate() methods. This is the equivalent of attaching a "mouseenter" and "mouseleave" event to an element. Really. That is all it does. If you said $("button").bind("mouseover mouseleave", myFunc) you can now say $("button").bind("hover", myFunc) and save 16 taps on your keyboard. Many people seem to just love it, and I don't understand why.

jQuery's hover event is not a hover event at all. First of all, unlike any other event, when the actual events occur the event.type is "mouseenter" or "mouseleave", not "hover". Then there is the API confusion, since there is also a .hover() method that can accept two functions (separate functions for each event). Some people expect that somehow the .live() event binding method knows about the powers of this magic hover event and thus it will accept two functions as well. But no. Thank goodness.

Then there is the question of what .trigger("hover") would do. Somehow this peculiar behavior didn't infect triggering, so that will truly trigger a custom event named "hover" and have no effect on the mouseenter-mouseleave "hover" that was bound. Of course, you'll have a mighty hard time binding an event handler to your custom "hover" event anyway. I suppose that for consistency and symmetry it should trigger mouseenter and mouseleave events in quick succession. But that would be silly, right?

More bothersome still is the misappropriation of the term "hover" for both of those cases. When an airplane flies through the airspace directly over my house, it's not "hovering" there. When a helicopter flies to the vicinity of my house and hangs out in mid-air over the roof, now that's hovering. The word "hover" implies that the mouse at least slowed down for a while and spent time over the element. With the current implementation, you'll know only that the mouse happened to touch the element for a fleeting time. You'll see many naive dropdown-menu implementations that use bare mouseenter-mouseleave events and act way too jumpy. Either they drop down as your mouse pointer flies by, or they quickly close if your mouse strays the slightest bit off course.

Brian Cherne's jQuery hoverIntent plugin takes mouse velocity and timing into account and follows that intuitive idea of what hovering really means. If your mouse-tracking needs exceed a simple mouseenter-mouseleave situation, the odds are that you need that plugin. One day I'd love to remove the "hover" hack and the special-case code that goes with it.