Unchecking Radio using Knockout Binding

The behaviour of knockouts check binding for radio buttons is useful if you have want to assign the value of the input tag into a observable like:

<input type="radio" value="Invoice" data-bind="checked:paymentOption">

But sometimes it is handy to to let the observable represent a boolean that checks/unchecks the radio button depending on its value

<input type="radio" value="Invoice" data-bind="checked:isSelected">

First note that there is no event firing upon un-checking, the event that is involved with a radio button selection is in in fact a change event on the group. Soo… How can we get around this in our knockout application? One approach is by adding a click binding that points to the parent of the view models that the radio buttons represent, like so:

<input type="radio" value="Invoice" data-bind="click: $parent.selectChild">

The selectChild method could look something like this:

self.selectChild = function (childViewModel) {
    for (var index = 0; index < self.childViewModels.length; index++) {
        var child = self.childViewModels[index];
        if (child.isSelected()) {
            child.isSelected(false);
        }
    }

    childViewModel.isSelected(true);
    return true;
};

The “return true” statement is a really funky thing in knockout that signals that you want the default action to be performed as well.

This leaves you with the expected behavior, but there is one more thing to do to get the nice finishing. Even though you have a boolean that can add/remove the checked attribute on the radio button, the ui will not be updated. The cleanest way I have found to solve this is to use the jQuery’s attr and remove checked (I know, it is wired… but jQuery does more things than just removing the attribute).

So as an addition to the click-binding I wrote a custom binding for just this:

ko.bindingHandlers.radio = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // if subscribe is not defined, it is likely that the valueAccessor is not an observable
        if (typeof valueAccessor().subscribe == "undefined")
            return;

        if ($(element).attr('type') != 'radio')
            return;

        // subscribe to the changes to the observable in the binding 
        valueAccessor().subscribe(function (newValue) {
            // if the new value is false, we interperate it as it should be unselected
            if (!newValue) {
                // ... so we update the ui with the jQuery hack
                $(element).attr("checked", false);
            } else {
                $(element).attr("checked", true);
            }
        });
    }
};

It subscribes to the observable in the binding-expression and when change, it adds/removes checked using jQuery. There is some safe-guarding there as well, so that it does nothing if the valueAccesor is not an observable and the DOM-element is not of type radio. This should prevent errors to occur.

With these pieces of the puzzle, the data-bind expression would be something like this:

<input type="radio" value="Invoice" data-bind="click: $parent.selectChild, radio: isSelected">

Happy coding!