Search code examples
arraysforeachknockout.jsdropdown

Knockout dropdown not updating after source array is updated


I have a dropdown that is populated from a view model's observable array. There is a button that is data-bound to a function that removes one of the elements from the array. When the button is clicked the array does a shift to remove an element. However, the dropdown does not reflect that the change and the dropdown values do not change.

html:

<!-- ko if: t -->
<select data-bind="template: { name: 'selectTemplateType', data: t }, value: selectedId"></select>
<!-- /ko -->
<!-- ko ifnot: t -->
<select>
    <option>No templates loaded</option>
</select>
<!-- /ko -->
<div data-bind='text: selectedId'></div>

<script type="text/html" id="selectTemplateType">
    <!-- ko foreach: groups -->
        <optgroup data-bind="attr: { 'label': name }, foreach: types">
            <option data-bind="value: id, text: name"></option>
        </optgroup>
    <!-- /ko -->
</script>

<div>
<button type="button" data-bind="click: remove">Remove First</button>
</div>

javascript:

var t = {
    groups: [
        { name: 'a', types: [{id: 1, name: 't1'}] },
        { name: 'b', types: [{id: 102, name: 't2'}, {id: 103, name: 't3'}] },
        { name: 'c', types: [{id: 5, name: 'x'}] }
    ]
}
var ViewModel = function() {
    var me = this;
    let temp = {
      groups: [
          { name: 'a', types: [{id: 1, name: 't1'}] },
          { name: 'b', types: [{id: 102, name: 't2'}, {id: 103, name: 't3'}] },
          { name: 'c', types: [{id: 5, name: 'x'}] }
      ]
      };
    me.t= ko.observable(ko.mapping.fromJS(temp));
    me.selectedId= ko.observable(103);
    
    me.remove = function(){
        me.t().groups().shift();
    }
};
var vm = new ViewModel();
ko.applyBindings(vm);

You can see the mess in action here: http://jsfiddle.net/Eves/2kw9y37t/24/

I assume I've missed something stupid. Anyone know what I'm missing?


Solution

  • If you look at the documentation, in Observable Arrays, you could read this:

    pop, push, shift, unshift, reverse, sort, splice

    All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change

    When you do:

    me.t().groups()
    

    you are operating on the underlying array, and then:

    me.t().groups().shift();
    

    it works ok, but then you don't notify listener about the change, and nothing changes in your html.

    You could write this:

    me.t().groups.shift();
    

    that is, calling the shift() method provided by Knockout in the observable array. Please, note that groups goes without parentheses.