Dustin Kingen
Dustin Kingen

Reputation: 21275

Knockout computed function not updating when value changed in binding

I've built a cascading dropdown with Knockout, but the cascading part (ko.computed) is not updating.

Knockout version is 3.0.0.

Preface:

I'm using a tree data structure to model the cascading.

{ id: '', text: '', childItems: [] }

Idea taken from:

knockout.js - nested array data and cascading pre-populated dropdown lists binding

Fiddle

Html:

<select data-bind="options: manufacturers,
                   optionsCaption:'Manufacturer',
                   optionsText: 'text',
                   optionsValue: 'id',
                   value: selectedManufacturer">
</select>
<select data-bind="options: models,
                   optionsCaption:'Model',
                   optionsText: 'text',
                   optionsValue: 'id',
                   value: selectedModel,
                   enable: enableModels">
</select>
<select data-bind="options: engines,
                   optionsCaption:'Engine',
                   optionsText: 'text',
                   optionsValue: 'id',
                   value: selectedEngine,
                   enable: enableEngines">
</select>

JS:

ViewModel:

function ViewModel(items) {
    this.manufacturers = ko.observableArray(items);
    // These three observables should be numbers (e.g. 1)
    // Corresponding to the id
    this.selectedManufacturer = ko.observable();
    this.selectedModel = ko.observable();
    this.selectedEngine = ko.observable();

    function getById(items, id) {
        return ko.utils.arrayFirst(items, function(item) {
            return item.id === id;
        });
    }

    this.models = ko.computed(function(){
        var items = ko.utils.unwrapObservable(this.manufacturers);
        var id = ko.utils.unwrapObservable(this.selectedManufacturer);
        return id ? getById(items, id).childItems : [];
    }, this);

    this.enableModels = ko.computed(function(){
        var items = ko.utils.unwrapObservable(this.manufacturers);
        var id = ko.utils.unwrapObservable(this.selectedManufacturer);
        return id ? getById(items, id).value > 0 : false;
    }, this);

    // generate engines based on models
    this.engines = ko.computed(function(){
        var items = ko.utils.unwrapObservable(this.models);
        var id = ko.utils.unwrapObservable(this.selectedModel);
        return id ? getById(items, id).childItems : [];
    }, this);

    this.enableEngines = ko.computed(function(){
        var items = ko.utils.unwrapObservable(this.models);
        var id = ko.utils.unwrapObservable(this.selectedModel);
        return id ? getById(items, id).value > 0 : false;
    }, this);
}

Data:

var items = [
    { text: 'Ford', id: 1, childItems:
     [
         { text: 'F-150', id: 1, childitems:
          [
              { text: 'Gasoline', id: 1, childitems: [] },
              { text: 'Diesel', id: 2, childitems: [] }
          ]
         },
         { text: 'F-250', id: 2, childitems:
          [
              { text: 'Gasoline', id: 3, childitems: [] },
              { text: 'Diesel', id: 4, childitems: [] }
          ]
         }
     ]
    },
    { text: 'Honda', id: 2, childItems:
     [
         { text: 'Civic', id: 5, childitems:
          [
              { text: 'Gasoline', id: 5, childitems: [] },
              { text: 'Electric', id: 6, childitems: [] }
          ]
         },
         { text: 'Accord', id: 6, childitems:
          [
              { text: 'Gasoline', id: 7, childitems: [] },
              { text: 'Electric', id: 8, childitems: [] }
          ]
         }
     ]
    }
];

Binding:

var module = {};

module.viewModel = new ViewModel(items);

ko.applyBindings(module.viewModel);

Update:

Here is the complete working sample based on the answer.

Upvotes: 1

Views: 700

Answers (1)

RP Niemeyer
RP Niemeyer

Reputation: 114792

Your issue is that in your "enable" computeds, you are returning the value property of item that is found. You would likely want to check if childItems exists and then if childItems has a length that is greater than 0.

Otherwise, you could remove the "enable" computeds, and just bind against the length of the options like enable: models().length (will be falsey if 0 length and truthy if greater than 0).

Here is an updated fiddle: http://jsfiddle.net/rniemeyer/GWVW8/1/

There were also some typos (childItems vs. childitems).

Upvotes: 2

Related Questions