Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 478
ShaneBlake Avatar asked Jan 17 '12 22:01

ShaneBlake


1 Answers

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()

like image 196
RP Niemeyer Avatar answered Oct 01 '22 13:10

RP Niemeyer