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
?
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.
- update viewmodel's value property.
- update view when value property programmatically changed (two-way binding).
- 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
Post a Comment