How to bind a checkbox to the inverse of a value?

2019-06-28 07:22发布

问题:

I have a case when I need to bind a checkbox and the visibility of another DOM element to the inverse of a boolean property of my viewModel:

<input type="checkbox" data-bind="checked: needsReview"> Not Required 
<br>
<div id="Some related text" data-bind="visible: needsReview"> other stuff here </div>
var dataFromSever = { needsReview: true };

var theModel = function (jsonData) {
    var self = this;
    ko.mapping.fromJS(jsonData, {}, self);
}

ko.applyBindings(new theModel(dataFromSever));

I have more than one property like this in my actual data model, so I do not want to make multiple ko.computed() fields. I'd just like to bind to "checked: !needsReview()" or something equally simple to maintain.

回答1:

There are a few similar ways to handle this one. The basic idea is that you need to create a writeable computed observable to bind the checkbox against.

You could do this in your model directly, using an extender, or by adding a function to the observable base (ko.observable.fn).

However, since you are using the mapping plugin and likely don't want to customize the way that your objects are created or add additional properties, I think that using a custom binding is the best option. Your model really does not need to be concerned with maintaining an inverse to your property, so we can actually do this part while binding.

Here is an inverseChecked binding that inserts a writeable computed observable between your real observable and the binding. Then, it simply uses the real checked binding to do its work.

ko.bindingHandlers.inverseChecked = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        var interceptor = ko.computed({
                            read: function() {
                                return !value();
                            },
                            write: function(newValue) {
                                value(!newValue);
                            },
                            disposeWhenNodeIsRemoved: element
                        }); 

        var newValueAccessor = function() { return interceptor; };


        //keep a reference, so we can use in update function
        ko.utils.domData.set(element, "newValueAccessor", newValueAccessor);
        //call the real checked binding's init with the interceptor instead of our real observable
        ko.bindingHandlers.checked.init(element, newValueAccessor, allBindingsAccessor);
    },
    update: function(element, valueAccessor) {
        //call the real checked binding's update with our interceptor instead of our real observable
        ko.bindingHandlers.checked.update(element, ko.utils.domData.get(element, "newValueAccessor"));  
    }
};

Here is a sample: http://jsfiddle.net/rniemeyer/Kz4Tf/

For your visible binding you can do visible: !needsReview()



回答2:

I generally make a custom bindingHandler in KO that is the inverse of visible:

ko.bindingHandlers['invisible'] = {

    update: function(element, valueAccessor){
        var val = ko.utils.unwrapObservable(valueAccessor());

        ko.bindingHandlers['visible'].update(element, function() { return !val; });            
    }
}

That allows you to cleanly take care of needs like you've described without cluttering your data-bind statements with ugly !val()



回答3:

I prefer the more generic and universal way of inverting an observable / computed value (that does not depend on any specific binding):

/**
 * A small handy observable / computed function that wraps around the inverse of it's boolean value.
 * @author Nir Azuelos
 * @returns {Function}
 */
ko.observable.fn.inverse = ko.computed.fn.inverse = function() {
    return ko.pureComputed(function() { return !this(); }, this);
};

Usage:

<div data-bind="visible: myObservable.inverse()">