Asle G
Asle G

Reputation: 588

An array filtering issue with knockout and selectors

I have an array of texts that I display in a <select>.

The texts may have different version no, and I want to filter the <select> based upon the latest version only.

I guess there are more elegant ways to do it (suggestions welcome), but I´ve chosen to use 2 <select>s set to alternate visibility depending on the checkbox.

The code is a hack, but the result looks pretty good. Unfortunately there´s a bug.

I have two observables indicating the selected option in their respective arrays:

self.SelectedText = ko.observable();
self.SelectedUnique = ko.observable();

Both have subscriptions, but I cannot link them together in both subscription, so I have chosen one to be indipendant on the other like this:

self.SelectedUnique.subscribe(function (text) {
    if (text) {
        self.SelectedText(text);
    }
});

However, the get out of sync.

Scenario 1: select text 1,2,3. [OK] Scenario 2: select text 2; check "Latest versions only"

This causes no options ("Choose…") to be displayed. Not what I want.

It gets worse.

Scenario 3: uncheck; select text 3; Then check "Latest versions only" again.

Now the select option chosen is set to select option no 2 of the unfiltered.

There´s probably a simple issue. I just can´t make it work probably. Here´s the fiddle: Fiddle: http://jsfiddle.net/h5mt51gv/6/

All help and suggestions appreciated!

Upvotes: 0

Views: 76

Answers (1)

Tomalak
Tomalak

Reputation: 338326

I have streamlined your approach:

  • the <select> binds to a computed list of options (visibleTextBatches)
  • this computed list depends on the state of the checkbox (latestOnly), effectively toggling between the full and the filtered list
  • the filtered list (latestTextBatches) is another computed that holds the latest version for each group
  • the <select> stores the actual selected TextBatch object in an observable (selectedTextBatch)
  • there is a subscription to visibleTextBatches that causes the latest selectable TextBatch to become the current one when the list is filtered. When the list is unfiltered, it does nothing.

function TextBatch(data) {
    this.textbatchId = data.textbatchId;
    this.parentId = data.parentId;
    this.version = data.version;
    this.title = ko.observable(data.title);
}

function ViewModel() {
    var self = this;

    // read up on the mapping plugin, too
    self.textBatches = ko.observableArray([
        new TextBatch({textbatchId: 1, parentId: 1, version: 1, title: "TB1.1"}),
        new TextBatch({textbatchId: 2, parentId: 1, version: 2, title: "TB1.2"}),
        new TextBatch({textbatchId: 3, parentId: 3, version: 1, title: "TB2.1"})
    ]);
    self.selectedTextBatch = ko.observable();
    self.latestOnly = ko.observable(false);
    self.latestTextBatchGroups = ko.computed(function () {
        var latest = {};
        ko.utils.arrayForEach(self.textBatches(), function (batch) {
            if (!latest.hasOwnProperty(batch.parentId) || 
                batch.version > latest[batch.parentId].version
            ) latest[batch.parentId] = batch;
        });
        return latest;
    });
    self.latestTextBatches = ko.computed(function () {
        return ko.utils.arrayFilter(self.textBatches(), function (batch) {
            return batch === self.latestTextBatchGroups()[batch.parentId];
        });
    });
    self.visibleTextBatches = ko.computed(function () {
        return self.latestOnly() ? self.latestTextBatches() : self.textBatches();
    });
    self.visibleTextBatches.subscribe(function () {
        var selectedBatch = self.selectedTextBatch();
        if (selectedBatch && self.latestOnly()) {
            self.selectedTextBatch(
                self.latestTextBatchGroups()[selectedBatch.parentId]
            );
        }
    });
}

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

<div>
    <select data-bind="
        options: visibleTextBatches, 
        optionsText: 'title',
        optionsCaption: 'Select...',
        value: selectedTextBatch
    " />
</div>

<div>
    <input type="checkbox" id="chkLatestOnly" data-bind="checked: latestOnly" />
    <label for="chkLatestOnly">Latest only</label>
</div>

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

Upvotes: 1

Related Questions