Knockout.js writeable computed boolean observable bound to checkbox - unreliable property setting

Tom W Hall picture Tom W Hall · Dec 10, 2012 · Viewed 8.2k times · Source

I have a checkbox which previously was bound directly to an observable property on my view model. I use a generic dirty flag on all view models which watches all observable properties on the view model for changes.

Now for one property I wish to present a confirmation alert if the user attempts to uncheck the checkbox before unsetting the underlying observable. I have tried to implement this as a writeable computed observable which wraps the observable like so:

function VM() {
    var self = this;

    self.SelectedInternal = ko.observable(false);

    self.selected= ko.computed({
        read: function () {
            return self.SelectedInternal();
        },
        write: function (value) {
            alert('Clicked'); // To demonstrate problem
            if (value === false) {
                if (confirm('Are you sure you wish to deselect this option?')) {
                    self.SelectedInternal(value);
                }          
            }
        }
    });
}

var vm = new VM();

ko.applyBindings(vm);

What I'm seeing (in both Firefox and IE) is that when I default the SelectedInternal observable value to false as above, then the "selected" write function only fires each time that I check the checkbox, not when I uncheck it. If I default the SelectedInternal observable value to true, then the first time I uncheck it the write setter executes, but not on subsequent unchecks.

Here's a fiddle to demonstrate:

http://jsfiddle.net/xHqsZ/18/

What is happening here? Is there a better way to achieve this?

UPDATE: This approach probably isn't going to work anyway because I can't get a hook into the original click to return false on, and resetting the observable to true if the user chose Cancel in the confirm box doesn't seem to take effect. But I'd still like to know why the computed setter itself isn't behaving as expected.

Answer

Tom W Hall picture Tom W Hall · Dec 12, 2012

I have so far achieved this thus (properties renamed for simplicity):

<input type="checkbox" data-bind="checked: vm.optionComputed, click: vm.toggleOption" />

And in the view model:

self.optionComputed = ko.computed({
    read: function () {
        return self.Option();
    },
    write: function (value) {
    }
});

self.toggleOption = function (vm, event) {
    var checkBox = $(event.target);
    var isChecked = checkBox.is(':checked');

    if (!(!isChecked && !confirm('Are you sure?'))) {
        self.Option(isChecked);
    }    
};

There's a slight glitchiness in that when you choose OK to unselect, the checkbox (which has already been blanked out by the click) briefly appears checked again before finally unchecking. But the behaviour in terms of preventing the observable changing until confirmation is correct.