Twitter Bootstrap getting state of checkbox button group
Asked Answered
D

1

11

There are several questions related to Twitter Bootstrap's button radio/checkbox groups, but none seem to be of the new Bootstrap convention (I'm using v3.3.4) or my addressing my question.

The old convention was to declare a <div class="btn-group"> that surrounded a number of <button>'s, and the surrounding div also had a data-toggle attribute of either buttons-checkbox or buttons-radio to look something like this:

<div class="btn-group" data-toggle="buttons-checkbox">
    <button type="button" class="btn btn-primary">Btn 1</button>
    <button type="button" class="btn btn-primary">Btn 2</button>
    <button type="button" class="btn btn-primary">Btn 3</button>
</div>

However, the convention for creating these kinds of groups has changed, as can be seen on the Bootstrap Javascript page for buttons (http://getbootstrap.com/javascript/#buttons-checkbox-radio):

<div class="btn-group" data-toggle="buttons">
  <label class="btn btn-primary active">
    <input type="checkbox" autocomplete="off" checked> Checkbox 1 (pre-checked)
  </label>
  <label class="btn btn-primary">
    <input type="checkbox" autocomplete="off"> Checkbox 2
  </label>
  <label class="btn btn-primary">
    <input type="checkbox" autocomplete="off"> Checkbox 3
  </label>
</div>

As you can see, the buttons are now actually labels with child input fields of type checkbox or radio that are styled to look like buttons.

I would like to set up a .on('click') event that captures the state of the button that has just been clicked, however the logic that follows is oddly backwards (and Javascript implementation of this is seemingly incomplete, at least in my eyes -- explanation to follow). Say, I'd like to set up a listener for the second label/checkbox/button and determine whether or not the label/checkbox/button is active/checked - I'd set up the following code:

$('#label2').on('click', function(){
  console.log($(this).hasClass('active'));
});

I would expect the above code to, when the button has been pressed to "activate" the label/checkbox/button, to print true to the console. It prints false. This is because the .on('click') listener gets fired before the button state changes, and therefore I would have to invert the logic for any actions I would want to take based on the button state. Seems backwards to me. (jsFiddle illustration: http://jsfiddle.net/mmuelle4/qq816po7/)

There are some other libraries out there that catch this the "sensible" way, such as the Bootstrap Switch library (http://www.bootstrap-switch.org/). The Bootstrap Switch library has a switchChanged event that catches a switch change after a click, but provide the state of the button/checkbox post-click.

I thought I had found a solution by registering a .on('change') handler rather than a .on('click') handler, but I got the same result.

Also, if the <label> is clicked, the nested <checkbox> never sets/unsets the checked property -- c'mon Bootstrap! Why even use the checkbox if the state is inert? Seems like a pretty clear case of inconsistency where the <label> can have a class of active but the <checkbox> not have the checked attribute set... (this is what I meant earlier when I said that the Javascript implementation of this is seemingly incomplete)

For now, I'll just have to invert the logic in my handler, but it just irks me. Overall, I guess my question is has anyone experienced this and found a better solution (still just utilizing jQuery and Bootstrap, no alternative libraries) that doesn't require inverted logic?

EDIT: There is a solution below, but my comment about the <checkbox> not having the checked attribute has spawned a slightly different question/discussion. One can perform an .is(':checked') query on the nested <checkbox> to get the correct state in a .on('change') handler, but if you inspect the DOM, that attribute is never actually set on the <checkbox>. It bothers me that the DOM doesn't reflect the state that I will receive with the .is(':checked') query...

EDIT2: Another odd quirk that I discovered is that, with the departure of using <button>'s for a radio or checkbox button group, different measures have to be taken to retrieve and change some of the properties of the "faux" buttons. In my case, I'd like to disable/enable buttons based on various user inputs, but setting the disabled property of the checkbox doesn't work:

$('input').prop('disabled', true) //Doesn't render the button disabled

This is because the button is now actually an <input> contained within a <label> which is styled as a button, so now disabled must be set on the <label>. This is a little frustrating, but not too hard to do with jQuery's .parent() method (because I'm storing a pointer to the input). What is frustrating is the fact that, in the case of a button, I can use the .prop('disabled', true/false) method to change the disabled property, but I cannot do the same because now it is a <label>.

This is described in jQuery's attr documentation (http://api.jquery.com/attr/), "To retrieve and change DOM properties such as the checked, selected, or disabled state of form elements, use the .prop() method." A <button> is apparently a form element, while a <label> is not (although I would think it would be). Now, in my code I have to consciously remember to use .prop('disabled', true/false) for real buttons, and .attr('disabled', true/false) for faux buttons. I'd like to see Bootstrap's reasoning for making the departure from regular buttons... (and maybe it's so that things like .is(':checked') can work, but it sure makes for some other headaches...)

EDIT3: Just learned that following the "new convention" of using <input>'s wrapped within <label>'s is not required. You can still use a button and the expected radio/checkbox behavior will occur... so much headache for nothing. Why would you not show off both options, Bootstrap???

Decile answered 15/4, 2015 at 16:2 Comment(0)
B
8

Boot strap already takes care of triggering events on the original elements that get converted. So you would not have to worry about checking the class.

All you have to do is set events for the original input and check state of that original input

$('label > input[type=checkbox]').on('change', function () {
    console.log($(this).is(':checked'));
});

DEMO: http://jsfiddle.net/qq816po7/1/

Boltzmann answered 15/4, 2015 at 16:11 Comment(5)
Great! This is as simple as it ought to be, but (maybe it's my eyes) I didn't see this described in the Bootstrap documentation at all! Apparently my testing wasn't quite accurate because the checked attribute does get set... I could've sworn I saw it sitting there not changing... either way - thanks!Decile
Actually, I'm positive that I saw it sitting there not changing! In chrome, if you open the developer tools in your linked jsfiddle and examine the label/checkbox, there is never a checkbox attribute that is applied to the <input>. This is why I assumed (I know I know, never assume) that an .is(":checked") test would return false. What black magic is going on here??Decile
yeah I also, checked it doesn't change, but that doesn't mean they aren't doing something in JS to get keep them in sync.Boltzmann
Sam, thanks for also testing. Agreed, they must be doing something in JS to keep them in sync, but that (maybe unnecessarily) bothers me that I can't see the same state reflected in the DOM.Decile
The issue where it is not changing in Chrome.... It actually is changing however just not how you expect... The DOM attribute checked actually corresponds to the javascript attribute defaultChecked, therefore calling $el.prop('checked', true') has no effect on the DOM as visible in firebug/chrome dev tools. If you click on the properties tab when the element is selected, and go to input, you'll see the actual checked attribute which can be different to the DOM attribute.... i.imgur.com/EDqE3Na.pngApteral

© 2022 - 2024 — McMap. All rights reserved.