Search code examples
javascriptknockout.jsko.observablearray

Knockout bind select to dynamic observable array


I've got a problem with dynamic binding to select control using knockout. What I want to achieve is a piece of code that binds the observableArray of observable objects to the select options. The 'objects' inside the observableArray change constantly.

The object stored in the observableArray, let's call it sampleObj, has a few properties (which are ko.observables) like name, lastName and phoneNumber. I would like to display as select options lastNames of all objects stored in the observableArray.

My binding to the observable inside the cshtml looks as follows:

<select data-bind="options: sampleObservableArray, optionsText: lastName, optionsCaption: 'choose...'"></select>

and it does not display anything besides the 'choose...' text. The content of the sampleObservableArray is updating in the js and I can console.log it whenever I want. Is there anything wrong in the way I bind the values or maybe should I somehow force refreshing of the select control? Of course as a complete amateur, I have tried I think all possible combinations of using and not using parethnesis :) but still with no effect.

sampleObservableArray - ko.observableArray that contains sampleObj's

sampleObj - ko.observable with a few properties like name, lastName and phoneNumber where all are ko.observables as well.

EDIT: Answering for @Tomalak's ask there is a code for better clarity. Firstly, sampleObj model:

define('sampleObj',
['ko'],
function (ko) {
    var
        SampleObj = function () {
            var self = this;
            self.name = ko.observable();
            self.lastName = ko.observable();
            self.phoneNumber = ko.observable();
            return self;
        };

    return SampleObj;
});

then adding the sampleObj objects to the observableArray:

// newData is an argument of a function triggered by signalR action, and returns strings as its properties
var sampleObj = ko.observable(new SampleObj()
    .name(newData.name)
    .lastName(newData.lastName)
    .phoneNumber(newData.phoneNumber);

var sampleObjEntry = ko.utils.arrayFirst(sampleObservableArray(), function (item) {
    return item().phoneNumber() === newData.phoneNumber;
});
// if object with the phoneNumber already exits replace it, if not - add
if (!sampleObjEntry) {
    sampleObservableArray.push(sampleObj);
} else {
    sampleObservableArray()[sampleObservableArray().indexOf(sampleObjEntry)] = sampleObj;
    sampleObservableArray.valueHasMutated();
}

Don't know if it helps.


Solution

  • I created simple JSFiddle Demo*, and it works as expected.
    I think the problem in your code is in line

    sampleObservableArray()[sampleObservableArray().indexOf(sampleObjEntry)] = sampleObj;
    

    You should remove sampleObjEntry from array and then push sampleObj or update already existent sampleObjEntry.

    For example:

    ...
    else {
        sampleObjEntry.name(newData.name);
        sampleObjEntry.lastName(newData.lastName);
        sampleObjEntry.phoneNumber(newData.phoneNumber);
    }  
    

    or

    ....
    else {
        sampleObservableArray.remove(sampleObjEntry);
        sampleObservableArray.push(sampleObj);
    }    
    

    *JSFiddle demo source

    Javascript

    function Person(name, lastName, phone)
    {
        return {
            name : ko.observable(name),
            lastName : ko.observable(lastName),
            phoneNumber : ko.observable(phone)
        }
    }
    
    var vm = {
        sampleObservableArray : ko.observableArray(),
        addPerson : function()
        {
           var newPhone = document.getElementById("_phone").value,
               newName = document.getElementById("_name").value,
               newLastName = document.getElementById("_lastName").value;
           var existentPerson = ko.utils.arrayFirst(vm.sampleObservableArray(), function(item)
                                                    {
                                                        return item.phoneNumber() == newPhone;
                                                    })
           if (existentPerson)
           {
               existentPerson.name(newName);
               existentPerson.lastName(newLastName);
               existentPerson.phoneNumber(newPhone);
           }
           else
           {
               vm.sampleObservableArray.push(new Person(newName, newLastName, newPhone))
           }
    
    
        }
    }
    
    vm.sampleObservableArray.push(new Person("Ronald", "McDonald", "1"))
    vm.sampleObservableArray.push(new Person("Michael", "McConnel", "2"))
    
    ko.applyBindings(vm)    
    

    Html

    <select data-bind="options: sampleObservableArray, optionsText: 'lastName', optionsCaption: 'choose...'"></select>  
    <br/>
    <label for="_name">Name:</label><input id="_name"/><br/>
    <label for="_lastName">Last Name:</label><input id="_lastName"/><br/>
    <label for="_phone">Phone:</label><input id="_phone"/><br/>
    <button data-bind="click: addPerson">Add person</button>