javascript - Knockoutjs Radio Button Group Custom Binding Not Updating on Selection -


i'm trying create custom binding in knockout functions options default binding handler uses radio buttons instead of dropdown.

adding item array notify update selecting different radio button not.

note: stripped custom binding handler bare minimum.

binding handler

// comment //<------------------------------ ko.bindinghandlers.radiobuttons = {     update: function (element, valueaccessor, allbindings) {         var unwrappedarray = ko.utils.unwrapobservable(valueaccessor());         var previousselectedvalue = ko.utils.unwrapobservable(allbindings().value);           // helper function         var applytoobject = function (object, predicate, defaultvalue) {             var predicatetype = typeof predicate;             if (predicatetype == "function")    // given function; run against data value                 return predicate(object);             else if (predicatetype == "string") // given string; treat property name on data value                 return object[predicate];             else                                // given no optionstext arg; use data value                 return defaultvalue;         };          // map array newly created label , input.         var isfirstpass = true;         var radiobuttonforarrayitem = function (arrayentry, index, oldoptions) {             if (oldoptions.length) {                 var item = oldoptions[0];                 if ($(item).is(":checked"))                     previousselectedvalue = item.value;                  isfirstpass = false;             }              var input = element.ownerdocument.createelement("input");             input.type = "radio";              var radiobuttongroupname = allbindings.get("groupname");             input.name = radiobuttongroupname;              if (isfirstpass) {                 var selectedvalue = ko.utils.unwrapobservable(allbindings.get("value"));                 var itemvalue = ko.utils.unwrapobservable(arrayentry.value);                 if (selectedvalue === itemvalue) {                     input.checked = true;                 }             } else if ($(oldoptions[0].firstelementchild).is(":checked")) {                 input.checked = true;             }              var radiobuttonvalue = applytoobject(arrayentry, allbindings.get("radiobuttonvalue"), arrayentry);             ko.selectextensions.writevalue(input, ko.utils.unwrapobservable(radiobuttonvalue));              var label = element.ownerdocument.createelement("label");             label.appendchild(input);             var radiobuttontext = applytoobject(arrayentry, allbindings.get("radiobuttontext"), arrayentry);             label.append(" " + radiobuttontext);              return [label];         };          var setselectioncallback = function (arrayentry, newoptions) {             var inputelement = newoptions[0].firstelementchild;             if ($(inputelement).is(":checked")) {                 var newvalue = inputelement.value;                 if (previousselectedvalue !== newvalue) {                     var value = allbindings.get("value");                     value(newvalue); //<------------------------- observable value , set here. shouldn't notify update?                 }             }         };          ko.utils.setdomnodechildrenfromarraymapping(element, unwrappedarray, radiobuttonforarrayitem, {}, setselectioncallback);     }, }; 

how use it...

// html <div data-bind="     radiobuttons: letters,      groupname: 'letters',      radiobuttontext: 'text',      radiobuttonvalue: 'value',        value: value,       "></div>  // javascript var vm = {     letters: ko.observablearray([         {             text: "a",             value: 1,         },         {             text: "b",             value: 2,         },     ]),     value: ko.observable(2), };  ko.applybindings(vm); 

so, adding new item vm.letters({ text: "c", value: 2 }) notify update. however, clicking on different radio button not notify update.

what need clicking on radio button notify update?

demo here

it doesn't binding has way update observable when selection changes. two-way binding isn't automatic custom bindings unless you're passing parameters through existing binding. event handlers "hack" precisely "init" function of custom binding used for.

the “init” callback

knockout call init function once each dom element use > binding on. there 2 main uses init:

  • to set initial state dom element
  • to register event handlers that, example, when user clicks on or modifies dom element, can change state of associated observable

try adding following init function radiobuttons binding:

ko.bindinghandlers.radiobuttons = {   init: function(element, valueaccessor, allbindings){     var unwrappedarray = ko.utils.unwrapobservable(valueaccessor());     var value = allbindings().value;      ko.utils.registereventhandler(element, "click", function(event){         var target = event.target;         if(target.nodename.tolowercase() == "input"){             value($(target).attr("value"));         }     });   },   update: function (element, valueaccessor, allbindings) {       ...   } 

there safer ways handle click event checking see if target "input" element; that's quick example. you'll have modify if ever have nested radio buttons or other types of input elements children within radiobutton element. if in doubt can copy knockout "checked" binding source code.

edit: things fix , how fixed them.

  1. update viewmodel's value property.
  2. update view when value property programmatically changed (two-way binding).
  3. if selected radio button removed, need set value property undefined.

there better way achieve of this...

ko.bindinghandlers.radiobuttons = {   init: function(element, valueaccessor, allbindings){     //... error checks ... remove child elements "element ...       var value = allbindings.get("value");      // 1. update viewmodel     ko.utils.registereventhandler(element, "click", function(event){         var target = event.target;         if(target.nodename.tolowercase() == "input"){             value(target.value);         }     });      // 2. update view      value.subscribe(function (newvalue) {         var inputs = element.getelementsbytagname("input")         $.each(inputs, function (i, input) {             input.checked = input.value === newvalue;         });     };   },   update: function (element, valueaccessor, allbindings) {       ...        var value = allbindings.get("value");        // 3. edge case: remove radio button of value selected.       var selectedradiobutton = unwrappedarray.find(function (item) {            return item.value === value();        });       if (selectedradiobutton == null) {           value(undefined);       }   } } 

Comments