Search code examples
javascriptknockout.jsknockout-mapping-plugin

With KnockoutJS, how to bind to a child property of an array item


I have a question similar to Bind text to property of child object and I am having difficulty properly creating the KO observable for a child object.

For example, I do an HTTP Get to return a JSON array of People, and the People array is inside a property called "payload". I can get the basic binding to work, and do a foreach on the payload property, displaying properties of each Person; however, what I need to do is add a "status" property to each Person, which is received from a different JSON, example

/api/people  (firstname, lastname, DOB, etc.)
/api/people/1/status   (bp, weight, height, etc)

I have tried binding to status.bp, and status().bp, but no luck.

The js example:

var TestModel = function (data) {
var len = data.payload.length;

for (var i = 0; i < len; i++) {

    var elem = data.payload[i];
    var statusdata = $.getJSON("http://localhost:1234/api/people/" + elem.id + "/status.json", function (statusdata) {

        elem.status = statusdata;
        data.payload[i] = elem;

        });
    }
    ko.mapping.fromJS(data, {}, this);
};

var people;

var data = $.getJSON("http://localhost:1234/api/people.json", function (data) {

    people= new TestModel(data);
    ko.applyBindings(people);
});

2 important things I will need: 1) properly notify KO that "payload" is an array to key on ID property 2) make "status" an observable

Help!

[UPDATE] EDIT with working fix based on Dan's answer:

var TestModel = function(data) {
...
this.refresh = function () {
    $.getJSON("http://localhost:1234/api/people", function (data) {

        self.payload = ko.observableArray(); // this was the trick that did it.

        var len = data.payload.length;

        for (var i = 0; i < len; i++) {

            var elem = data.payload[i];

            $.getJSON("http://localhost:1234/api/people/" + elem.id + "/status", function (statusdata) {

                // statusdata is a complex object
                elem.status = ko.mapping.fromJS(statusdata);
                self.payload.push(elem);
            });
        }
    // apply the binding only once, because Refresh will be called with SetInterval
    if (applyBinding) {  
        applyBinding = false;
        ko.applyBindings(self);
    }
}

I am still new to Knockout and improvements to the refresh function are most welcome. The mapping is still being reapplied each time.


Solution

  • You need to define an observable array and then push your data into it.

    elem.status = ko.observableArray();
    
    for (var i = 0; i < statusdata.length; i++) {
        elem.status.push(statusdata[i]);
    }
    

    I can't tell what the full structure of the data is by the example. But if status is a complex object, you may what to give it its own model.

    for (var i = 0; i < statusdata.length; i++) {
        elem.status.push(new statusModel(statusdata[i]));
    }