tags:

views:

79

answers:

4

Hi... jQuery beginner here.

Here is what I'm working on. I have an area map with 10 hotspots on it. Hover over each of the hotspots and it shifts the background of the div (id=dialpad) to display other data (in a sprite).

The code I have currently works, but I have a separate function for each hotspot ID.

Ex:

    $('#dial1')
    // On mouse over, move the background on hover
   .mouseover(function() {
     $('#dialpad').css('backgroundPosition', '0 -120px');
   })
   // On mouse out, move the background back
   .mouseout(function() {
     $('#dialpad').css('backgroundPosition', '0 0');
   })
     $('#dial2')
    // On mouse over, move the background on hover
   .mouseover(function() {
     $('#dialpad').css('backgroundPosition', '0 -240px');
   })
   // On mouse out, move the background back
   .mouseout(function() {
     $('#dialpad').css('backgroundPosition', '0 0');
   });
   ...

What I want to do is to consolidate that code into a single function where I simply pass the vertical offset figure from each area ID.

Can someone assist?

A: 
function setupDial(selector, offset) {
  function resetPos() {
     $('#dialpad').css('backgroundPosition', '0 0');
  }
  function setPos(int p) {
    return function() {
       $('#dialpad').css('backgroundPosition', '0 ' + p + 'px');
    }
  }

  $(selector)
    // On mouse over, move the background on hover
    .mouseover(setPos(offset))
   // On mouse out, move the background back
   .mouseout(resetPos);
}

setupDial('#dial1', -120);
setupDial('#dial2', -240);
sje397
+5  A: 
$('#dial1,#dial2').hover(
    function() { changePosition('0 ' + (parseInt(this.id.match(/\d+$/)[0])) * -120 + 'px'); },
    function() { changePosition('0 0'); }
);

function changePosition(position) {
   $('#dialpad').css('backgroundPosition', position);
}

Update:

Alternatively, if the "hotspots" occur on the page in the same order as the index numbers, then you could take a slightly more efficient approach and give them all a class and use the index property of .each() to determine the proper index number.

$('.hotspots').each(function( idx ) {
    $(this).hover(
        function() { changePosition('0 ' + ((idx + 1) * -120) + 'px'); },
        function() { changePosition('0 0'); }
    );
});

function changePosition(position) {
   $('#dialpad').css('backgroundPosition', position);
}

In fact, you really don't need the changePosition() function if you don't want it. The code is short enough that a little repetition is not a big deal.

$('.hotspots').each(function( idx ) {
    $(this).hover(
        function() { $('#dialpad').css('backgroundPosition', '0 ' + ((idx + 1) * -120) + 'px')},
        function() { $('#dialpad').css('backgroundPosition', '0 0'); }
     );
});​
patrick dw
Neat...but doesn't let you 'pass the vertical offset'.
sje397
@sje397 - Sure it does. The result of the `parseInt()` is multiplied by `-120`. I just forgot to add the `px` to the string. Updating...
patrick dw
$().hover() is shorthand for mouseenter/mouseleave, not mouseover/mouseout. On the one hand I appreciate the brevity of that solution... but regex matching to determine the offset? Mixed feelings...
jmar777
@jmar777 - Yes, that is true, but `mouseenter/mouseleave` is more often what is desired/recommended. Since each "hotspot" referenced in the question is meant to trigger the `#dialpad` once, there's no need to continually trigger it for child elements by using `mouseover/mouseout`.
patrick dw
This is very slick. Works perfectly and even cleaner than I thought, since I don't have to pass any variables. Much thanks!
jrhaberman
@jmar777 - With regard to the regex, without seeing the markup it is the only way I know to determine the index. If I knew that the order of the "hotspots" on the page matched the order of the indexes, I would rather put the code in an `.each()` and use the `index` property of the each. Maybe I'll update with that possibility.
patrick dw
@patrick: Indeed - I agree that .hover() is probably what he was looking for. Just wanted to be sure that OP was aware of the difference.
jmar777
for the record, each hotspot is ID'd "dial1"..."dial10" and they are in the same order within the html. I don't know if this makes a difference.
jrhaberman
@jrhaberman - I just updated with another solution that could be a little better. Since you stated that they appear in the proper index order on the page, just give the hotspots a class, and iterate over them using `.each()`. This will give you an index number you can use instead of relying on the index in the `id` attribute.
patrick dw
@jmar777 - Good point. I should have pointed that out in my answer.
patrick dw
@patrick awesome. Just implemented your update and it is completely solid. Thanks again. You've saved me a number of hours of fumbling around blindly today. :)
jrhaberman
@jrhaberman - You're welcome. Also, you could actually get rid of the `changePosition()` function too and just call the `$('#dialpad').css()` directly.
patrick dw
+1  A: 

Cleanest way is to make a quick plugin:

(function($){
  $.fn.mysprite = function(options){
    //merge the provided options with defaults
    var o = $.extend($.fn.mysprite.defaults, options||{});

    // this is the main body, here we iterate over the set 
    // returned by the selector applying the new functionality 

    this.each(function(){
      switch(typeof o.target){
         case 'object':
         case 'string':
           var target = $(o.target);
           break;
         case null:
           var target = $(this);
           break;
         default:
           // no target specified!
           return this;   
      }

      //simplify your previous code with the hover method

      $(this).hover(
        function(){
          target.css('backgroundPosition', o.offsetIn);
        }, 
        function(){
          target.css('backgroundPosition', o.offsetOut);
        });
    });

    return this;
  };

  // setsup class defaults
  $.fn.mysprite.defaults = {
    offsetIn: '0 0',
    offsetOut: '0 0',
    target: null
  };
})(jQuery);

//invoke with

$('#dialpad1').mysprite({offsetIn: '0 100', offsetOut: '0 0', target: '#dialpad'});
prodigitalson
Nice work, a bit overkill, but nice. You might want to correct the typo in your first line though.
Norbert de Langen
Well, i suppose the `switch` was a little extraneous... Thanks for the heads up on the typo - fixed.
prodigitalson
+1  A: 

Here's a variation using closures/function-generator for the callbacks:

function setOffset(offset) {
    return (function() { $('#dialpad').css('backgroundPosition', offset); });
}       
$('#dial1,#dial2').each(function(i) {
    $(this).hover(setOffset('0 -' + (120 * (i+1)) + 'px'), setOffset('0 0'));
});
jmar777
+1 - I like the returning function approach, but remember that you will need to do `i + 1` in the `.each()` since it is zero-based indexing. :o)
patrick dw
Doh! Good call - updated.
jmar777