views:

81

answers:

6

I want to make it so when I click somewhere in my website, the background changes. I have three backgrounds, and I want to make a loop of them.

$(document).ready(function() {
 $('body').click((function(){
  return function()
  {
   if (counter == null) {
    var counter = 1;
   }
   if(counter == 3) {
     $(this).css("background-image","url(3.jpg)");
     $(this).css("background-position","10% 35%");
     var counter = null;
   }
   if(counter == 2) {
     $(this).css("background-image","url(2.jpg)");
     $(this).css("background-position","10% 35%");
     var counter = 3;
   }
   if(counter == 1) {
     $(this).css("background-image","url(1.jpg)");
     $(this).css("background-position","40% 35%");
     var counter = 2;
   }


  }
 })());

});

Why doesn't this work?

A: 

Does this code work?

var counter = 1;
$(document).ready(function() {
    $('body').click(function() {
        if (counter == null) {
            counter = 1;
        }
        if (counter == 3) {
            $(this).css("background-image", "url(3.jpg)");
            $(this).css("background-position", "10% 35%");
            counter = 1;
        }
        if (counter == 2) {
            $(this).css("background-image", "url(2.jpg)");
            $(this).css("background-position", "10% 35%");
            counter = 3;
        }
        if (counter == 1) {
            $(this).css("background-image", "url(1.jpg)");
            $(this).css("background-position", "40% 35%");
            counter = 2;
        }
    });
});
drachenstern
+1  A: 

this no longer refers to the body element, it refers to the anonymous function.

prodigitalson
+5  A: 

Your counter variable isn't scoped right, you need one counter variable. Overall though, why not let .toggle() manage this for you? Here's what it would look like:

$(function() {
  $('body').toggle(function(){
     $(this).css({"background-image":"url(1.jpg)", "background-position":"40% 35%"});
  }, function() {
     $(this).css({"background-image":"url(2.jpg)", "background-position":"10% 35%"});
  }, function() {
     $(this).css({"background-image":"url(3.jpg)", "background-position":"10% 35%"});
  });
});

Although the name and common usages suggest that .toggle() only takes 2 functions, it actually takes 2 or more and will cycle through them.

Nick Craver
To much redundancy ... but I won't down-vote, that would be to harsh
Šime Vidas
@Sime - What's reundant? You don't know *for sure* if he's using a background color, etc...so you can't just use `"background"`, and the position isn't the same on all of them.
Nick Craver
@Sime: The redundancy makes it much easier to understand for someone like the OP who's trying to learn JS. It would be a good exercise for the OP to try and remove some of the redundancy and would help him understand whats happening in the code...
Aardvark
You don't see the redundancy? Let's say that you want to also change the background-attachment or some other CSS property defined on the body element (and you have more than a dozen of images that you toggle). How hard would it be to set this new property on every click. Look at my answer, and you will see that I only set $(this).css() once.
Šime Vidas
@Sime - *If* it gets more complicated then sure, use an array of objects and pass one of *those* to `.css()`, but as it stands your code is longer and more complicated than this. Don't over-complicate the simple things for some hypothetical scenario, address the question at hand :)
Nick Craver
@Aardvark It would be even better if we (the guys who answer the questions on Stack Overflow) would supply the OP with good JavaScript code. It is a myth that beginners should be served sub-optimal code so that they can learn in steps. No, the OP should get the best possible code. Only then he can truly learn to code good JavaScript. The above code is bad because of its redundancy. It saddens me that people on this forum do not have high standards and that they approve such code :(
Šime Vidas
@Sime - I personally find that for only 3 items this is much more readable, maintainable and all around simpler. It's fine that you disagree, use what you want, but for only 3 items I think this is *much* easier to deal with. Your answer is inefficient for a few reasons (e.g. multiple `var` declarations, no reason for the extra closure, why not use `counter++`, why store the image name - use the index, `moduluous` is easier...). Where are *your* high standards? :) I'm kidding of course, just showing your counter-example isn't a model of being completely optimal either :)
Nick Craver
@Sime - Also keep in mind that people weren't necessarily voting *only* on my code, most of the other answers *don't address the question* (which is the most important part of an answer): "Why doesn't this work?"
Nick Craver
@Nick I just don't tolerate redundancy. I think that it's a bad practice and that it should be avoided.
Šime Vidas
@sime while I agree with your general point about quality of information in our responses, there is nothing wrong with the code given here. Your and Nick's answers take two very different approaches: yours is more toward a vanilla implementation that can be easily moved across libraries, where Nick took a jquery-esk approach. Nick's code is much more terse than yours where as yours is easily expandable. In my book, they are both valid, both equally readable, and neither is better than the other.
Justin Johnson
@Nick Well, the closure is needed, of course. The function which gets executed on click uses the imgs and counter variables which are bound to it through that closure.
Šime Vidas
@Sime - It's not needed really, since you're dealing with `body` specifically those variables can be dealt with much easier in the `ready` handler scope directly....you can't have 2 `<body>` elements.
Nick Craver
@Sime - While I appreciate your commitment to the best possible code, I think the general consensus is readability and maintainability tends to be worth a little repetition.
Aardvark
@Nick Well, I did't want to pollute the ready handler namespace. Especially, since one of the vars is a counter, and counters are pretty common. The body click handler is the only function that uses those two vars anyway, so I'm more comfortable with them being private to the click handler.
Šime Vidas
@Nick I updated my answer with another solution. I took the redundancy out of your code.
Šime Vidas
@Sime - I'll stick with my version, it's *much* easier to maintain and read IMO. Everyone has their opinions, but I think most will sacrifice a *little* redundancy (notice I said *little*, very little in this case) for *much* easier to maintain code.
Nick Craver
I think I can agree with that :)
Šime Vidas
A: 

Your function uses this which is refering to itself, not the element. This would fix it:

$('body').click((function(){
    var $this = $(this);
    return ... {
        $this // use $this instead of $(this)

Also, have a look on jQuery .toggle

BrunoLM
A: 

Your counter declarations are strewn all over the place which makes it difficult to follow what's happening. Further, counter is declared local to the callback function, which means it loses its value every time the function executes.

Here's a simpler solution:

$(function() {  // this is equivalent to $(document).ready(...)
  var counter = 0;
  var images = [
    [ '1.jpg', '40% 35%' ],
    [ '2.jpg', '10% 35%' ],
    [ '3.jpg', '10% 35%' ]
  ];
  $('body').click(function() {
    $(this).css('background-image', 'url(' + images[counter][0] + ')');
    $(this).css('background-position', images[counter][1]);

    // increment counter, wrapping over to 0 when it reaches end of array
    counter = (counter + 1) % images.length;
  });
});

You can easily extend to this to any number of images by simply adding more entries to the images array.

casablanca
The background position isn't the same for all the `if` statements ;)
Nick Craver
Oops, didn't notice that. Fixed using an array instead.
casablanca
Wow, I also came up with that same approach where I place the data in an array. I did not copy it from you.
Šime Vidas
A: 
Šime Vidas