Binding radio buttons to observableArray with single element

1,055 views
Skip to first unread message

sgar...@silverbacklearning.com

unread,
Apr 2, 2014, 5:55:42 PM4/2/14
to knock...@googlegroups.com
I don't know if this is a bug in the 3.0 library or if this was an intentional change, but this is a "feature" that broke between the versions.  In 2.0 you could do this:

var viewModel = {
    enable
: ko.observable(true),
    selected
: ko.observableArray(['Y'])
};

ko
.applyBindings(viewModel);

<p> Current Radiobutton Value = <span data-bind='text: selected' ></span></p>

<input type='radio' name='onlyOne' data-bind='checked: selected, enable: enable' value='Y' /> Yes
<input type='radio' name='onlyOne' data-bind='checked: selected, enable: enable' value='N' /> No
<input type='radio' name='onlyOne' data-bind='checked: selected, enable: enable' value='M' /> Maybe



However, in 3.0 this fails to bind properly.  This has caused us problems because we have a data structure that is sometimes single choice (radio buttons) and sometimes multiple choice (checkboxes) and so of course the model is an array that sometimes has one element and sometimes has multiple elements.  Does anyone A) know of a simple way around this problem, and B) if this was an intentional change in Knockout?

Here is a fiddle that demonstrates this:

http://jsfiddle.net/omnius/BgQPF/137/

Thanks,

Message has been deleted

sgar...@silverbacklearning.com

unread,
Apr 2, 2014, 6:14:32 PM4/2/14
to knock...@googlegroups.com, sgar...@silverbacklearning.com
After I posted this I re-tested it and my simplified fiddle actually worked under Knockout 3.0, but only for update, not the initial bind (so the radio button wasn't checked when the form comes up, but if you select a radio button the model *will* update).  So, my premise is wrong and this is likely a real bug.  If anyone has an idea, I'm all ears.

Michael Best

unread,
Apr 3, 2014, 4:24:29 AM4/3/14
to knock...@googlegroups.com, sgar...@silverbacklearning.com
When binding to radio buttons, the checked value should be a plain observable and not an observable array.

selected: ko.observable('Y')

-- Michael

Michael Best

unread,
Apr 3, 2014, 4:26:13 AM4/3/14
to knock...@googlegroups.com, sgar...@silverbacklearning.com

sgar...@silverbacklearning.com

unread,
Apr 3, 2014, 6:06:39 PM4/3/14
to knock...@googlegroups.com, sgar...@silverbacklearning.com
Michael,

Thanks for the reply.  I'm aware that works and that's what the samples on the knockout web site show (though I would argue that a sample is not the same as documenting that it is the only way to call it), but it clearly worked with an array with a single element in KO 2.0 and it's very convenient to us to work with it that way.  We did find a way to make it work and I'm about to post our solution.  I would love to see the interface (and the samples) for radio buttons extended to officially support an observableArray with a single value.

Scott

sgar...@silverbacklearning.com

unread,
Apr 3, 2014, 6:25:06 PM4/3/14
to knock...@googlegroups.com, sgar...@silverbacklearning.com
Below is the solution we have decided to use.  It relies on a custom binding handler to set the radio button from the observableArray on the way in, then on the way out we use checkedValue to create the proper array with the correct single value.  We use a custom comparer to make sure the proper radio button gets set.  Thanks to Michael Best for sample code on StackOverflow for leading me to this solution.

http://stackoverflow.com/questions/20622744/using-checkedvalue-binding-with-radio-buttons

<div data-bind="foreach:list">
   
<label>
       
<input type="radio" data-bind="
            checkedValue: [$data],
            radioChecked: $parent.checkedVal,
            checkedComparer: $parent.itemCompare
        "
/>
       
<span data-bind="text: $data"></span>
   
</label>
   
<br />
</div>
<span data-bind="text: JSON.stringify(checkedVal())"></span>


ko.bindingHandlers.radioChecked = {
    init: function (element, valueAccessor, allBindings)
    {
        ko.utils.registerEventHandler(element,
                                      "click",
                                      function ()
                                      {
                                          if (element.checked)
                                          {
                                              var observable = valueAccessor(),
                                              checkedValue =  allBindings.get('checkedValue');
                                              observable(checkedValue);
                                          }
                                      });
    },
    update: function (element, valueAccessor, allBindings)
    {
        var modelValue = valueAccessor()(),
                         checkedValue =  allBindings.get('checkedValue'),
                         comparer = allBindings.get('checkedComparer');
       
        element.checked = comparer(modelValue, checkedValue);
    }
};

function viewModel ()
{
    this.list = [
                "One",
                "Three",
                "Five"
                ];
    this.checkedVal = ko.observableArray(["Three"]);
    this.itemCompare = function(a, b)
    {
        return JSON.stringify(a) == JSON.stringify(b);
    }
}

ko.applyBindings(new viewModel())



Here is a working fiddle:




On Wednesday, April 2, 2014 3:55:42 PM UTC-6, sgar...@silverbacklearning.com wrote:
Reply all
Reply to author
Forward
0 new messages