How to Make a Tri-State Checkbox Using KnockoutJS

I want to start by saying, “Don’t do this!”

Now that we got that out of the way, here is how you would do it and when you might consider using it. First the code:

Knockout

(function () {
    ko.bindingHandlers.triState = {
        init: function (element, valueAccessor) {
            element.onclick = function () {
                var value = valueAccessor();
                var unwrappedValue = ko.unwrap(value);

                if (unwrappedValue) {
                    value(null);
                } else if (unwrappedValue === false) {
                    value(true);
                } else {
                    value(false);
                }
            };
        },
        update: function (element, valueAccessor) {
            var value = ko.unwrap(valueAccessor());

            if (value) {
                markChecked(element);
            } else if (value === false) {
                markUnchecked(element);
            } else {
                markUnspecified(element);
            }
        }
    };

    function markChecked(el) {
        el.readOnly = false;
        el.indeterminate = false;
        el.checked = true;
    }

    function markUnchecked(el) {
        el.readOnly = false;
        el.indeterminate = false;
        el.checked = false;
    }

    function markUnspecified(el) {
        el.readOnly = true;
        el.indeterminate = true;
        el.checked = false;
    }
}());

Html

<input type="checkbox" data-bind="triState: value" />

JavaScript

var vm = {
    value: ko.observable()
};

ko.applyBindings(vm);

In the Knockout tab I am creating a simple knockout binding handler that adds a click handler when it is initialized and then on update it will change the state of the checkbox.

The click handler updates the value by rotating through null, false, and true. It sets the value of the observable to the next value in the rotation and then it is done.

The update portion of the binding handler checks the newly set value of the observable and sets the state of the checkbox accordingly.

When the value is true, the checkbox is checked.

When the value is false, the checkbox is unchecked.

When the value is null, the checkbox is unspecified, which is denoted by the checkbox being readonly and indeterminate. Being indeterminate marks the checkbox as a third hidden state that already exists in the browser.

Now, when would you use this? I would hope never. The indeterminate state of a checkbox has a completely valid usage. That usage is as part of a tree. When some but not all of the items on a lower branch of the tree have been checked then the state of the current node of the tree is correctly indeterminate. In the case of the tri-state checkbox, it is a design smell. If you find you want to use this or someone on your team wants to use this, you can of course use the code I have presented. I would suggest however, to instead find a better way to solve the problem. Checkboxes are supposed to be either true or false and having a third state really messes with that paradigm.

Leave a Reply

Your email address will not be published. Required fields are marked *