86

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?

C.J.
  • 6,789
  • 7
  • 36
  • 45
  • 10
    For Knockout versions >= 3.0 see [Natan's answer](http://stackoverflow.com/a/20764607/606662) for a simpler soution than suggested by the accepted answer. – Markus Pscheidt Feb 07 '14 at 20:37

8 Answers8

133

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"/>
Natan
  • 4,686
  • 5
  • 30
  • 48
  • 2
    Looking at the source, it seems to make a decision to use the checked value [here](https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/checked.js#L16). The 'useCheckedValue' is set to true [if the input is a radio or a checkbox with the value as an array](https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/checked.js#L78). Also, I'm using Knockout 3.0. See if that helps. – Natan Jan 22 '14 at 12:50
  • 1
    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 ;-) – ZiglioUK Jan 22 '14 at 21:15
  • 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](http://knockoutjs.com/documentation/checked-binding.html). – Jason Frank Feb 13 '14 at 00:35
  • @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 – Simon_Weaver Aug 04 '14 at 00:39
  • 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. – wirble Jun 15 '16 at 17:16
  • Somebody, please mark this as "the answer" - it's very clean. – Slawomir Sep 01 '17 at 14:15
  • 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 Sep 20 '17 at 20:00
  • Ignore the prior comment. Must of had a caching issue. This works like a charm! – Stephen McCormick Sep 20 '17 at 20:21
81

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/

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • 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. Apr 12 '12 at 16:32
  • If you are using the mapping plugin, then you would have to use the `create` callbacks (http://knockoutjs.com/documentation/plugins-mapping.html#customizing_object_construction_using_create) in the mappings or add the ForEditing computed observable after your data has been initialized (and after new data has been added). – RP Niemeyer Apr 12 '12 at 17:17
  • 26
    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: http://jsfiddle.net/rniemeyer/utsvJ/ – RP Niemeyer Apr 12 '12 at 17:25
  • Updated JS, because the managed resource on both of the above jsfiddles are 404'ing: http://jsfiddle.net/utsvJ/12/ – xdumaine Oct 19 '12 at 12:30
  • 2
    +1 to using a binding instead of creating an observable on every object – Greg Ennis Mar 29 '13 at 03:49
  • @RPNiemeyer's solution works awesome for me using the mapping plugin – Andy Brudtkuhl May 17 '13 at 17:00
  • 1
    @RPNiemeyer is definitely the way to go. – Andrew Jul 31 '13 at 21:15
  • Here is another version that uses extenders instead of bindings: http://jsfiddle.net/siimv/mQ7CU/ May come handy if you have different bindings where you have to use boolean's string value. – Siim Aug 14 '13 at 18:51
  • 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? – Adam Levitt Dec 10 '13 at 14:18
  • 1
    @RPNiemeyer thanks! that fiddle in the comments is the one that worked for me. – SFlagg Dec 17 '15 at 15:55
11

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>
Artem
  • 3,700
  • 1
  • 27
  • 35
  • 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) – Artem Apr 12 '12 at 16:10
  • 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. – C.J. Apr 12 '12 at 16:40
  • 1
    @C.J. this converts string or number to boolean - chek this http://jsfiddle.net/nickolsky/6ydLZ/ ,if string is already bool it will keep it as bool – Artem Apr 12 '12 at 16:46
  • Thanks very much. I think this is a great solution too. – C.J. Apr 12 '12 at 17:31
  • 1
    It doesn't work both ways though. The radio buttons aren't checked when loaded. – Mikael Östberg Dec 02 '13 at 13:27
  • 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. – Artem Dec 02 '13 at 17:09
1
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>
KurtJ
  • 19
  • 2
1

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.

Greg Little
  • 3,360
  • 2
  • 19
  • 16
0

Why not simply true and false instead of 1 and 0?

 <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>
Doctor
  • 57
  • 10
0

You can also use an extender so it's easy to reuse them for more observables:

ko.extenders.boolForEditing = function (target, allowNull) {
    var result = ko.computed({
        read: function () {
            var current = target();
            var newValue = null;
            if (current === undefined || current === null || current === '') {
                if (!allowNull) {
                    newValue = 'false';
                }
            } else {
                newValue = current ? 'true' : 'false';
            }
            return newValue;
        },
        write: function (newValue) {
            var current = target();
            var valueToWrite = null;
            if (newValue === undefined || newValue === null || newValue === '') {
                if (!allowNull) {
                    valueToWrite = false;
                }
            } else {
                valueToWrite = newValue === 'true';
            }
            // only write if it changed
            if (valueToWrite !== current) {
                target(valueToWrite);
            } else {
                if (newValue !== current) {
                    target.notifySubscribers(valueToWrite);
                }
            }
        }
    }).extend({
        notify: 'always'
    });

    result(target());

    return result;
};

Then use it like this:

this.IsMale.forEditing = this.IsMale.extend({boolForEditing: true});

The parameter provided to boolForEditing indicates whether the value may be null.

See http://jsfiddle.net/G8qs9/1/

sroes
  • 14,663
  • 1
  • 53
  • 72
0

After doing lot of research for older version of knockout prior to 3.0 there are possibly two best options

Create a knockout extender like

ko.extenders["booleanValue"] = function (target) {
    target.formattedValue = ko.computed({
        read: function () {
            if (target() === true) return "True";
            else if (target() === false) return "False";
        },
        write: function (newValue) {
            if (newValue) {
                if (newValue === "False") target(false);
                else if (newValue === "True") target(true);
            }
        }
    });

    target.formattedValue(target());
    return target;
};

To use the extender on your model, you’d do something like the following:

function Order() {
  this.wantsFries= ko.observable(false).extend({ booleanValue: null });
}

<span>Do you want fries with that?</span>
<label>
  <input type="radio" name="question" value="True"
             data-bind="value: wantsFries.formattedValue" /> Yes
</label>
<label>
  <input type="radio" name="question" value="False"
             data-bind="value: wantsFries.formattedValue" /> No
</label>

source:http://www.timlabonne.com/2013/02/building-a-knockout-js-extender-for-boolean-values/

Daniele Armanasco
  • 7,289
  • 9
  • 43
  • 55
Anshul Nigam
  • 1,608
  • 1
  • 12
  • 26