Binding true / false to radio buttons in Knockout

2020-01-27 10:23发布

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?

8条回答
男人必须洒脱
2楼-- · 2020-01-27 11:09

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/

查看更多
欢心
3楼-- · 2020-01-27 11:13

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"/>
查看更多
Evening l夕情丶
4楼-- · 2020-01-27 11:15

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/

查看更多
Ridiculous、
5楼-- · 2020-01-27 11:18

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.

查看更多
三岁会撩人
6楼-- · 2020-01-27 11:21
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>
查看更多
放荡不羁爱自由
7楼-- · 2020-01-27 11:23

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>
查看更多
登录 后发表回答