0
votes

I've got a jsfiddle to illustrate roughly what I'm experiencing in my application code. (I'll post the actual fiddle code below, for posterity.)

Essentially, I've got one observable array that's being used to populate a list of checkbox options. Selecting one of the options adds that item to another observable array composed of just the selected items.

If there's some error in the form (or I suppose if you were editing existing data), the selected observable array is populated from the start with the previously selected items. However, now, there is no connection between the original items and the selected items, so checking or unchecking one of the checkboxes adds a duplicate and only removes that duplicate.

My question is 1) am I going about this completely wrong (i.e. is there a better way to do this that won't cause this problem) and 2) if not, what's the best way to restore the connection between the items, so that the checkboxes will indicate that the items are there and unchecking them will remove the item?

HTML

<ul data-bind="foreach: Fruit">
    <li>
        <label>
            <input type="checkbox" class="cbFruit" data-bind="checked: Selected, attr: { value: Name }" />
            <span data-bind="text: Name"></span>
        </label>
    </li>
</ul>

<ul data-bind="foreach: SelectedFruit">
    <li data-bind="text: Name"></li>
</ul>

JS

var FruitViewModel = function (name) {
    var self = this;

    self.Name = ko.observable(name);
    self.Selected = ko.observable(false);

    return self;
};

var GroceryStoreViewModel = function () {
    var self = this;

    self.Fruit = ko.observableArray([
        new FruitViewModel('Apples'),
        new FruitViewModel('Oranges'),
        new FruitViewModel('Bananas'),
        new FruitViewModel('Pineapples')
    ]);

    self.SelectedFruit = ko.observableArray([
        new FruitViewModel('Oranges'),
        new FruitViewModel('Bananas')
    ]);

    return self;
};

$(document).ready(function () {
    var viewModel = GroceryStoreViewModel();
    ko.applyBindings(viewModel);

    $('.cbFruit').on('click', function () {
        var fruit = ko.dataFor(this);
        if (fruit.Selected()) {
            viewModel.SelectedFruit.push(fruit);
        } else {
            viewModel.SelectedFruit.remove(fruit);
        }
    });
});
1
Did you have a look at the selectedOptions binding in conjunction with a multi-select list? (Not quite an answer to your question, still related, in a way.) - Jeroen
I would use the Selected property to also track the initial selection and change the SelectedFruit to a computed... jsfiddle.net/QLdzG/1 - nemesv

1 Answers

0
votes

I solved the issue by simply throwing away the separate observable array (and the separate posted list). Instead, I'm using the same full list for both foreach uses and in the second one, using a Knockout virtual if element, to have it only show the selected ones:

HTML

<ul data-bind="foreach: Fruit">
    <li>
        <label>
            <input type="checkbox" class="cbFruit" data-bind="checked: Selected, attr: { value: Name }" />
            <span data-bind="text: Name"></span>
        </label>
    </li>
</ul>

<ul data-bind="foreach: Fruit">
    <!-- ko if: Selected -->
    <li data-bind="text: Name"></li>
    <!-- /ko -->
</ul>

The data from the second list posts with the first, so I have to do a little clean-up server-side to make sure that the content-only values (name of fruit, etc) come back if the view is shown again.