I am trying to style a checkbox list. I've added my styles and they appear correctly when rendered. I want to add a class when the label for the checkbox is clicked. This is my markup and here is the same in a jsfiddle. You can see from my fiddle that two click events are registered with just one click. Why?
html:
<ul>
<li>
<label for="test_0" class="">
<input id="test_0" name="offering_cycle" type="checkbox" value="1"> Fall
</label>
</li>
<li>
<label for="test_1" class="">
<input id="test_1" name="offering_cycle" type="checkbox" value="2"> Spring
</label>
</li>
<li>
<label for="test_2" class="">
<input id="test_2" name="offering_cycle" type="checkbox" value="3"> Summer
</label>
</li>
<li>
<label for="test_3" class="">
<input id="test_3" name="offering_cycle" type="checkbox" value="4"> Other
</label>
</li>
</ul>
CSS:
ul {
list-style-type:none;
}
label {
position:relative;
display:inline-block;
padding-left:27px;
height:25px;
}
label:before {
display:block;
position:absolute;
top:-2px;
margin-left:-28px;
width:18px;
height:18px;
background-color:#fff;
border-radius:5px;
border:1px solid #ccc;
text-align: center;
color:#fff;
font-size:18px;
content:'a';
}
input {
width:1px;
height:1px;
border:0;
opacity:0;
float:right;
}
jQuery:
$('label[for*=test_]').on('click',function(){
$(this).toggleClass('testing');
});
Reason the label's click handler is called twice:
Clicking on a label that is associated with an input causes two click events to be triggered. The first click event is triggered for the label. The default handling of that click event causes a second click event to get triggered for the associated input. Since you have the input as a descendant of the label, the second click event bubbles up to the label. That is why your click event handler is called twice.
If you really want to handle a click event for the label (and have it execute only once for a click):
(1) If you are willing and able to modify the HTML, you could move the input so it is not a descendant of the label. There will still be two click events, but the second click event will not bubble up from the input to the label since the label is no longer an ancestor of the input.
When the input is not a descendant of the label, you must use the label's "for" attribute to associated it with the input. The value of the "for" attribute should be the "id" value of the input. (You are already including the "for" attribute with the proper value.)
(2) Preventing the default handling of the first click event prevents the second click event from getting triggered, BUT doing this breaks the label. That is, the checkbox will not get checked/unchecked when the label is clicked.
jsfiddle
(3) Instead, you could stop the second click event from bubbling up from the input.
jsfiddle
Note: If the input was not a child of the label, this would not be necessary.
(4) Or you could check the event target in the handler. It will be the label for the first click event and the input for the second. The following handler executes the code inside the if-statement only for the first click event.
jsfiddle
Note: If the input was not a child of the label, the code above would still work, but the if-statement would be unnecessary because the click event triggered for the input would not bubble up to the label.
Handling the click for the input instead:
In your case, you don't really need to register a click handler for the label element. You could register a click (or change) handler for the input instead. You could then use
$(this).closest('label')
to get the label element.jsfiddle
Note: If the input was not a child of the label, the handler above would still get called when you click on the label, but
$(this).closest('label')
would not get the label. You would have to use something like$('label[for="' + this.id + '"]')
instead.Regarding the "for" attribute on the label elements:
Since you have the inputs inside the labels, it is not necessary to include the
for
attributes on the labels --- but it's not invalid.You have set the "for" attribute values to the values of the "id" attributes of the input elements. That is the correct way to use the "for" attribute to associated a label with an input. If you were to include a "for" attribute with an invalid value, the label would not be associated with the input, even if the input is a descendant of the label.
From the HTML5 spec for the "for" attribute of a label element: