Benjamin Allison
Benjamin Allison

Reputation: 2154

KnockoutJS: collecting values of checkboxes and selects in array together

I'm really pulling my hair out on this one. I need to loop over some selected items, consisting both of checkboxes and select lists, and compile an array of the selected models.

This is eluding me because checked and the select's value work differently. I was trying to simply store the selected option in a parent model (with a selectedOption observable). This worked fine for <select> because it saved a reference to the model, but with checkboxes, it would only save a boolean value.

I tried setting a flag on the model itself, which is fine for checkboxes, but I can't access individual options to let them know that they're the current selection in a select list.

All models populating checkboxes and dropdown lists are of the same type, I'm just displaying them differently depending on certain internal properties.

How can I simply collect the models of all selected options from both checkboxes and select lists?

Edit: Adding samples. There's an Item model which is the parent for an Option model.

function ItemModel(vm, item) {
    var self = this;
    self.name = item.name;
    self.description = item.description;
    self.options = ko.observableArray(mapOptions(vm, item.options) || []);
    self.selectedOption = ko.observable();
}

function OptionModel(vm, option) {
    var self = this;
    self.name = option.name;
    self.sku = option.sku || "";
    self.price = ko.observable();
}

Markup/bindings (in which I foolishly thought I could set the checked value of the checkbox to simply be the model). If there's a single Option I use a checkbox, if more than one, I use a select list.

<input type="checkbox" data-bind="checked: $parent.selectedOption, checkedValue: $data" />

<select data-bind="value: selectedOption,
options: options,
optionsText: item.name
optionsCaption: 'Choose...'">
</select>

Again, the <select> works as expected: the value is a reference to the currently selected Option, which means I can use that data elsewhere in the app (to tally up a summary of the customer's order). The checkbox, however, only saves a boolean value.

Upvotes: 0

Views: 903

Answers (2)

Michael Best
Michael Best

Reputation: 16688

I think the preferred way to solve this is with a custom binding. Here's one I put together that should do what you need.

ko.bindingHandlers.valueWhenChecked = {
    init: function (element, valueAccessor) {
        if (!ko.isWritableObservable(valueAccessor().model)) {
            throw Error("model must be an observable");
        }
        ko.utils.registerEventHandler(element, "change", function () {
            var model = valueAccessor().model;
            model(element.checked ? valueAccessor().value : undefined);
        });
    },
    update: function (element, valueAccessor) {
        element.checked = valueAccessor().model() === valueAccessor().value;
    }
};

function ItemModel() {
    this.options = ko.observableArray(["test"]);
    this.selectedOption = ko.observable();
}

ko.applyBindings(new ItemModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>

<input type="checkbox" data-bind="valueWhenChecked: { model: selectedOption, value: options()[0] }" />

<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>

Upvotes: 1

Roy J
Roy J

Reputation: 43881

How about you add an isSelected member, with a subscription that sets selectedOption to the first option when true? Then use the isSelected as your checkbox value.

Upvotes: 0

Related Questions