Search code examples
knockout.jsko.observablearray

Knockout: Adding to observable Arrays in Nested ViewModels


I have a few nested view models with observable arrays and am unable to successfully add to any of the nested arrays. Adding at the top-level works fine. I've done lots of reading and fiddling to try to troubleshoot what I'm doing wrong but to no avail.

The hierarchy is as follows: Queue-->Files-->Claims-->Lines

I can add new files to a queue but I can't get new claims added (tried via File and via Queue)

Any Ideas what I'm doing wrong? http://jsfiddle.net/bxfXd/254/

UPDATE: All answers below pointed me to the core issue - using raw data instead of instantiated models in my observable arrays. updated fiddle with working code: http://jsfiddle.net/7cDmg/1/

HTML:

<h2>
    <span data-bind="text: name"></span>        
    <a href='javascript:' data-bind="click: addFile">AddFile</a>
</h2>
<div data-bind="foreach: files">
    <h3>File: <span data-bind="text: name"></span> (<span data-bind="text: id"></span>)
    <a href='javascript:' data-bind="click: $data.addClaim">AddClaimViaFile</a>
    <a href='javascript:' data-bind="click: $root.addClaim">AddClaimViaQueue</a></h3> 

    <div data-bind='foreach: claims'>
        <h3 data-bind="text: ud"></h3> 
        <table border=1>
            <thead>
                <td>id</td>    <td>procedure</td>    <td>charge</td>
            </thead>
            <tbody  data-bind='foreach: lines'>
                <tr>
                    <td data-bind="text: id"></td> 
                    <td data-bind="text: procedure"></td> 
                    <td data-bind="text: charge"></td>
                </tr>
            </tbody>
        </table>
    </div>
</div>​

JavaScript:

var line1 = { id: 1, procedure: "Amputate", charge: 50 };
var line2 = { id: 2, procedure: "Bandage", charge: 10};
var claim1 = { ud: '1234E123', charge: 123.50, lines: [line1,line2] };
var claim2 = { ud: '1234E222', charge: 333.51, lines: [line2,line2] };
var file1 = { id: 1, name: "Test1.txt", claims: [claim2] };
var file2 = { id: 2, name: "Test2.txt", claims: [] };
var queue = { id: 1, name: "JoesQueue", files: [file1,file2] }; 


function Line(data) {
    this.id = ko.observable(data.id);
    this.procedure = ko.observable(data.procedure);
    this.charge = ko.observable(data.charge);
}

function Claim(data) {
    this.ud = ko.observable(data.ud);
    this.charge = ko.observable(data.charge);
    this.lines = ko.observableArray(data.lines);
}

function File(data) {
    var self=this;
    self.id = ko.observable(data.id);
    self.name = ko.observable(data.name);
    self.claims = ko.observableArray(data.claims);
    self.addClaim = function(file) {  //this never gets called.. Why?
        alert("File.addClaims");
       self.claims.push(claim1);
    }          
}

function Queue(data) {
    this.id = ko.observable(data.id);
    this.name = ko.observable(data.name);
    this.files = ko.observableArray(data.files);
    this.addClaim = function(file) {
        alert("Queue.addClaim");
        console.log(file);
       file.claims.push(claim1); //This line gets hit, but no claims get added..Why?
    };     
    this.addFile = function() {
        alert("Queue.addFile");
        this.files.push(file2); //Works - adding seems to only work at the top level :(
    }
}

$(function () {
    ko.applyBindings(new Queue(queue));
});


Solution

  • I quickly checked your jsfiddle and I see that there are a few problems like:

    • the initial data you pass to Queue() constructor is not made of observables. In other words the initial queue.files observable array contains raw JS objects, and not instances of your File viewmodel.

    • data-bind="click: File.addClaim" doesn't do what you want it to do. It should be data-bind="click: addClaim", but first your queue.files array has to consist of File instances.

    I fixed those problems in this jsfiddle: http://jsfiddle.net/ntXJJ/2/ (forked from yours).