Wednesday, December 16, 2009

Quick Tip: Outline Elements on Hover

Quick Tip: Outline Elements on Hover:

Someone on the jQuery Google Group yesterday asked about how to display a border around elements on hover. Here is a quick script I wrote to achieve that effect:

[inline][/inline]
JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext')
  3. .mouseover(function(event) {
  4. $(event.target).addClass('outline-element');
  5. })
  6. .mouseout(function(event) {
  7. $(event.target).removeClass('outline-element');
  8. })
  9. .click(function(event) {
  10. $(event.target).toggleClass('outline-element-clicked');
  11. });
  12. });

The script will respond to mouseover, mouseout, and click within <div class="entrytext">. I use mouseover/mouseout rather than mouseenter/mouseleave because the script relies on event bubbling. The $(event.target) selector ensures that the class manipulation occurs on the innermost element. You can try it out on this page by hovering over elements within the main content area.

The two classes that are being toggled have the following style rules:

CSS:
  1. .outlineElement {
  2. outline: 1px solid #c00;
  3. }
  4. .outlineElementClicked {
  5. outline: 1px solid #0c0;
  6. }

The outline property is really handy for this sort of thing because it doesn't affect the element's dimensions or layout. Unfortunately, it isn't supported in Internet Explorer 6 or 7, so if you want to support them, you might want to use border or background-color instead in a separate IE-only stylesheet. Or, maybe IE has some proprietary property? Anyone know?

I'm not sure what the person wanted to do with this script. By itself, it doesn't seem particularly useful to me. Still, I think there are a few concepts in there that can be applied elsewhere, and maybe the script can be expanded to do something interesting.

Update

After thinking about this for a night, I realized that the script could be written more succinctly by combining the mouseover and mouseout event types in a single .bind() method and toggling the class therein …

JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext')
  3. .bind('mouseover mouseout', function(event) {
  4. var $tgt = $(event.target);
  5. if (!$tgt.closest('.syntax_hilite').length) {
  6. $tgt.toggleClass('outline-element');
  7. }
  8. })
  9. .click(function(event) {
  10. $(event.target).toggleClass('outline-element-clicked');
  11. });
  12. });

Notice that the updated script also takes advantage of the .closest() method, which was introduced in jQuery 1.3. I don't want to apply the outline to any of the syntax-highlighted code in the entry, so I make sure that neither the event target nor any of its ancestors has a class of 'syntax_hilite.' Remember, to test for the presence (or absence) of a selector, we need to include more than just the selector itself. Here I test for 'not a length.' What I'm actually looking for is a length of 0, but since 0 is 'falsey' the ! operator works just fine here.

One More Tip

Come to think of it, I can make the script shorter still by including the click event type along with mouseover and mouseout. Then, inside the the handler, the class that is toggled is determined by event.type.

JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext').bind('mouseover mouseout click', function(event) {
  3. var $tgt = $(event.target);
  4. if (!$tgt.closest('.syntax_hilite').length) {
  5. $tgt.toggleClass(event.type == 'click' ? 'outline-element-clicked' : 'outline-element');
  6. }
  7. });
  8. });

Sometimes when code becomes shorter, it can also get less readable and more difficult to modify later on. I'm not suggesting that the final code snippet is necessarily the best way to achieve the outcome. A lot depends on what else you're planning to do and how a certain piece fits into the project as a whole.