Binding true / false to radio buttons in Knockout JS

50,415

Solution 1

One option is to use a writeable computed observable.

In this case, I think that a nice option is to make the writeable computed observable a "sub-observable" of your IsMale observable. Your view model would look like:

var ViewModel = function() {
   this.IsMale = ko.observable(true);

   this.IsMale.ForEditing = ko.computed({
        read: function() {
            return this.IsMale().toString();  
        },
        write: function(newValue) {
             this.IsMale(newValue === "true");
        },
        owner: this        
    });          
};

You would bind it in your UI like:

<label>Male
   <input type="radio" name="IsMale" value="true" data-bind="checked:IsMale.ForEditing"/>
</label> 
<label>Female
   <input type="radio" name="IsMale" value="false" data-bind="checked:IsMale.ForEditing"/>
</label>

Sample: http://jsfiddle.net/rniemeyer/Pjdse/

Solution 2

I know this is an old thread, but I was having the same problem and found out a much better solution that was probably added to knockout after this question was officially answered, so I'll just leave it for people with the same problem.

Currently there is no need for extenders, custom binding handlers or computeds. Just provide a "checkedValue" option, it will use that instead of the html 'value' attribute, and with that you can pass any javascript value.

<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: true"/>
<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: false"/>

Or:

<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 1"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 2"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 3"/>

Solution 3

This works for me:

http://jsfiddle.net/zrBuL/291/

<label>Male
   <input type="radio" name="IsMale" value="1" data-bind="checked:IsMale"/>
</label> 
<label>Female
   <input type="radio" name="IsMale" value="0" data-bind="checked:IsMale"/>
</label>

Solution 4

Once you figure out that the initial match for the radio button wants to match only a string and wants to set the value to a string, it is simply a matter of converting your initial value to string. I had to fight this with Int values.

After you have setup your observables, convert the value to string and KO will do its magic from there. If you are mapping with individual lines, do the conversion in those lines.

In the example code, I'm using Json to map the whole Model in a single command. Then letting Razor insert the value between the quotes for the conversion.

script type="text/javascript">
    KoSetup.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));
    KoSetup.ViewModel.ManifestEntered("@Model.ManifestEntered");       //Bool
    KoSetup.ViewModel.OrderStatusID("@Model.OrderStatusID");           //Int
</script>

I use a "Dump it all to the screen" at the bottom of my web page during development.

<h4>Debug</h4>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>

Here are the data values, Before

"OrderStatusID": 6,
"ManifestEntered": true,

and, After

"OrderStatusID": "6",
"ManifestEntered": "True",

In my project, I didn't need to convert Bools, because I'm able to use a checkbox that doesn't have the same frustration.

Solution 5

ko.bindingHandlers['radiobuttonyesno'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) {
        var stateHandler = function (property, allBindingsAccessor, key, value, checkIfDifferent) {
            if (!property || !ko.isObservable(property)) {
                var propWriters = allBindingsAccessor()['_ko_property_writers'];
                if (propWriters && propWriters[key])
                    propWriters[key](value);
            } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
                property(value);
            }
        };

        var updateHandler = function () {
            var valueToWrite;

            if ((element.type == "radio") && (element.checked)) {
                valueToWrite = element.value;
            } else {
                return; // "radiobuttonyesno" binding only responds to selected radio buttons
            }

            valueToWrite = (valueToWrite === "True") ? true : false;

            var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue); //can be true of false

            stateHandler(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
        };
        ko.utils.registerEventHandler(element, "click", updateHandler);

        // IE 6 won't allow radio buttons to be selected unless they have a name
        if ((element.type == "radio") && !element.name)
            ko.bindingHandlers['uniqueName']['init'](element, function () { return true });
    },
    'update': function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());

        value = value ? "True" : "False";

        if (element.type == "radio") {
            element.checked = (element.value == value);
        }
    }
};

Use this binder instead of creating stupid ko computed observables.

Example:

<label>Male
        <input type="radio" name="IsMale" value="True" data-bind="radiobuttonyesno:IsMale"/>
     </label> 
     <label>Female
        <input type="radio" name="IsMale" value="False" data-bind="radiobuttonyesno:IsMale"/>
     </label>
Share:
50,415

Related videos on Youtube

C.J.
Author by

C.J.

Updated on October 31, 2020

Comments

  • C.J.
    C.J. over 3 years

    In my view model I have a IsMale value that has the value true or false.

    In my UI I wish to bind it to the following radio buttons:

    <label>Male
       <input type="radio" name="IsMale" value="true" data-bind="checked:IsMale"/>
    </label> 
    <label>Female
       <input type="radio" name="IsMale" value="false" data-bind="checked:IsMale"/>
    </label>
    

    The problem I think is checked expects a string "true" / "false". So my question is, how can I get this 2-way binding w/ this UI and model?

    • Markus Pscheidt
      Markus Pscheidt over 10 years
      For Knockout versions >= 3.0 see Natan's answer for a simpler soution than suggested by the accepted answer.
  • Artem
    Artem about 12 years
    the only problem I see is that when serializing viewmodel to pass to server, you will get integers in observable (instead of booleans). You will need to call vm.IsMale(!!vm.+IsMale()); before serializing json to send to server (in case server side cannot handle it properly)
  • C.J.
    C.J. about 12 years
    Seems like a great solution. I use the mapping pluggin to refresh my VM. Do you know it that would wipe out ForEditing on IsMale?
  • C.J.
    C.J. about 12 years
    I think you are right, but my Javascript knowledge is lacking. Can you please explain to me how !!+ works? I'm not familiar w/ that syntax.
  • Artem
    Artem about 12 years
    @C.J. this converts string or number to boolean - chek this jsfiddle.net/nickolsky/6ydLZ ,if string is already bool it will keep it as bool
  • RP Niemeyer
    RP Niemeyer about 12 years
    If you are using the mapping plugin, then you would have to use the create callbacks (knockoutjs.com/documentation/…) in the mappings or add the ForEditing computed observable after your data has been initialized (and after new data has been added).
  • RP Niemeyer
    RP Niemeyer about 12 years
    Here is an alternative that pushes the computed observable creation into a custom binding, which would make it so you don't have to worry about dealing with it in the mapping plugin: jsfiddle.net/rniemeyer/utsvJ
  • C.J.
    C.J. about 12 years
    Thanks very much. I think this is a great solution too.
  • xdumaine
    xdumaine over 11 years
    Updated JS, because the managed resource on both of the above jsfiddles are 404'ing: jsfiddle.net/utsvJ/12
  • Greg Ennis
    Greg Ennis about 11 years
    +1 to using a binding instead of creating an observable on every object
  • Andy Brudtkuhl
    Andy Brudtkuhl about 11 years
    @RPNiemeyer's solution works awesome for me using the mapping plugin
  • Doctor
    Doctor about 11 years
    if you are sending json objects it makes difference.
  • Andrew
    Andrew almost 11 years
    @RPNiemeyer is definitely the way to go.
  • Siim
    Siim almost 11 years
    Here is another version that uses extenders instead of bindings: jsfiddle.net/siimv/mQ7CU May come handy if you have different bindings where you have to use boolean's string value.
  • Mikael Östberg
    Mikael Östberg over 10 years
    It doesn't work both ways though. The radio buttons aren't checked when loaded.
  • Artem
    Artem over 10 years
    it works, this was just old fiddle - fixed it. At some point github does not allow linking of raw files from jsfiddle, so some old fiddles no longer working.
  • Adam Levitt
    Adam Levitt over 10 years
    This solution definitely works, but it seems like such a hoop to have to jump through! Is binding to true/false with radio buttons that uncommon that it's not part of the knockout core functionality?
  • Natan
    Natan over 10 years
    Looking at the source, it seems to make a decision to use the checked value here. The 'useCheckedValue' is set to true if the input is a radio or a checkbox with the value as an array. Also, I'm using Knockout 3.0. See if that helps.
  • ZiglioUK
    ZiglioUK over 10 years
    yeah, thanks, I've upgraded to 3.0.0 and now it's there. Still need to wrap my boolean values in a String because the server code was expecting those ;-)
  • Jason Frank
    Jason Frank over 10 years
    Good info. Two additional points: (1) I found that with pre-v3.0 Knockout I was able to use the value binding and then the checked binding (in that order), but with 3.0 I had to use the checkedValue binding instead of the value binding. (2) pre-v3.0 was picky about requiring the value binding to precede the checked binding to function correctly, so I have a feeling that things might also work better in v3.0 in all scenarios if you put the checkedValue binding before the checked binding, like they show in the docs.
  • aruno
    aruno almost 10 years
    @ZiglioNZ one way this can fail is if your checked setter (or maybe something dependent on it) raises an error - then the correct checkbox isn't checked
  • SFlagg
    SFlagg over 8 years
    @RPNiemeyer thanks! that fiddle in the comments is the one that worked for me.
  • wirble
    wirble about 8 years
    I am using version 3.4 and finds that I still have to put in value="true" and the above to get it to work.
  • Slawomir
    Slawomir almost 7 years
    Somebody, please mark this as "the answer" - it's very clean.
  • Stephen McCormick
    Stephen McCormick almost 7 years
    One issue with this is that it leaves the radio button with the "false" unchecked. I really was looking to toggle between the two buttons and visually have it hold the checked. I needed add a method to reset the checked radio buttons.
  • Stephen McCormick
    Stephen McCormick almost 7 years
    Ignore the prior comment. Must of had a caching issue. This works like a charm!