views:

316

answers:

3

I've got the following HTML

<li><input type="checkbox" /><label>This is the label!</label></li>

I bound a click event to the li, and let the click event bubble up to the li before doing anything else.

$('li').each(function(i){
  var item = $(this);
  var checkbox = $("input[type='checkbox']", item);

  item.bind('click', function(e){
    var isChecked = checkbox.is(':checked');
    console.log(isChecked);
    e.stopPropagation();
  });
});

Starting with an unchecked checkbox, when the click event fires, isChecked returns true when I click on the checkbox, but returns false when I click on the label or li. Does anyone know why?

[EDIT:] To demonstrate the issue, have a look at this http://jsfiddle.net/nYbf6/2/.

  1. Click on the checkbox in the first li.
  2. Click inside the second li, but not on a label or checkbox.

Notice that you can never toggle the checkbox by clicking on the checkbox because the 'checked' attribute always returns what it is not when clicking on the checkbox.

[EDIT:] Found the answer here today: http://www.bennadel.com/blog/1525-jQuery-s-Event-Triggering-Order-Of-Default-Behavior-And-triggerHandler-.htm. I'm not fond of the solution, but it's better than no solution.

A: 

You would do better to fetch the appropriate checkbox as part of the click, like this:

$('li').click(function(e) {
  var isChecked = $(':checkbox:checked', this).length > 0;
  console.log(isChecked);
  e.stopPropagation();
});

This finds the checkbox in the <li> that was clicked when it was clicked.

As for the why part, if you're clicking on the checkbox, you're setting it to true, then the event propagates to the li which determines the checkbox is indeed checked. With an unchecked box if you're clicking on the label, it's not checking the box, so it's still false. If you checked the box then clicked the label, you'll see true coming back, because now it's checked.

If you're expecting the label to check the checkbox, you need to relate it using label's for attribute, like this:

<li>
  <input id="myInput" type="checkbox" />
  <label for="myInput">This is the label!</label>
</li>

Then the browser will handle the connection, click the label will toggle the checkbox.

Nick Craver
No. The appropriate checkbox is already referenced.
Kappers
@Kappers - I was saying it's easier, not that yours was wrong :) Here's your code running and it looks fine: http://jsfiddle.net/CsQf7/ Check clicking the label and it's *unchecked* it's false, then *checked* it's true...this is correct behavior. Were you looking to toggle the checkbox when anywhere in the `<li>` is clicked?
Nick Craver
I'm not expecting the label to trigger anything [edit: I don't expect the label to toggle the element using the for attr]. When any child element is clicked, I'm letting the click event bubble to the li, and then specifying what happens next; apply validation rules then check or uncheck the checkbox depending on if validation passed and the current checked attr. Relying on the event handler I'm binding to do all of the work.
Kappers
@Kappers - I'm very confused as to what you want. Currently if it's checked, no matter what triggered the click it's true, if it's not checked, it's false. Clicking the checkbox itself **toggles the value** so of course it'll change each click there, and it'll output whether it's **now** checked or not.
Nick Craver
Click an unchecked checkbox, console.log spits true. Click a label for an unchecked checkbox, console.log spits out false and then true because it triggers the click for the checkbox. But, since I'm overriding the label, it returns false when unchecked checkbox: [EDIT: correct link: http://jsfiddle.net/p4Yz6/]
Kappers
FF 3.6. What I want is for any element clicked to report the same 'checked' attr. In this example, you see the checkbox reports one thing when clicked, and the label reports something different: http://jsfiddle.net/p4Yz6/
Kappers
@Kappers - When you click a checkbox it *is* checked **when you check the value**. **After** this click event fires, the `e.preventDefault()` removes the checked attribute that was applied before the event bubbled.
Nick Craver
This should clear everything up: http://jsfiddle.net/nYbf6/2/Forget about the label. Click on the checkbox in the first LI, then click in the second LI, but not on a label or checkbox. LI1 returns true, LI2 returns false. I've got to know what the correct value is intially to be able to uncheck or check the checkbox manually.
Kappers
@Kappers - I still see what you're talking about, unfortunately this is **how a checkbox works** you're seeing the value a click is *trying* to set it to. If you don't want that, you're better off showing an image and not having the actual checkbox visible, since you want it to not have a click event itself (which the checkbox inherently does).
Nick Craver
In the code, I'm not asking for the value that the click event is trying to set, I'm asking for the value explicitly set on the checkbox. There's no reason why asking for the attr should give me the value the event is trying to set when it fires.
Kappers
@kappers - I don't know how to explain it any better to you, when you click a checkbox it toggles, that's **what a checkbox does**. You're then toggling it **back** in code, reversing the value. If you don't want something that toggles it's value when clicked, **don't use a checkbox**.
Nick Craver
+1  A: 

the click event of a checkbox fires before the value is changed. This way, if you return false when you override the click event, it stops the checkbox from changing it's value. You should change the event to fire onclick of the checkbox, not the li that contains it.

Mike Sherov
+1 - this is why the `onchange` event is better for check boxes.
Andy E
This is incorrect for this question...the click event isn't on the checkbox, it's on the parent, at that point the value is set correctly.
Nick Craver
@Nick Craver, thanks for the input. I've updated my answer to be more clear as to what I meant.
Mike Sherov
@Mike - I believe in this case the OP wants a click on the `<li>` to toggle the checkbox (common scenario), see my answer for an example. The common mistake is forgetting the 2 elements need to be linked to accomplish it, or bind it via JS, either or.
Nick Craver
@Andy, trying to use click only to keep from binding many events.@Mike, is there a clean way to override this behavior? I'd like the checked attribute to return the same value no matter what element is clicked.
Kappers
+1  A: 

Try this:

<li>
  <input type='checkbox' value='foo' id='myFavoriteCheckbox'>
  <label for='myFavoriteCheckbox'>The Label</label>
</li>

Now when you click on the label, the checkbox will be affected. Bind the handler to the checkbox and check .attr('checked') and you'll find that even on click events the value of the checkbox will be correct. Note also that that's true even if you decide to stop propagation and prevent default for the event. In other words, if you handle a click event and decide to prevent the default action, your handler will still see the value of "checked" set to what it would be if you did not stop the default action.

Pointy