David Brown
David Brown

Reputation: 23

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));
});

Upvotes: 2

Views: 5532

Answers (3)

Tomasz Zieliński
Tomasz Zieliński

Reputation: 16367

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).

Upvotes: 3

Keith Nicholas
Keith Nicholas

Reputation: 44316

Also, another clean up of your code with manual mapping of your datamodel

http://jsfiddle.net/keith_nicholas/d64TN/

Upvotes: 0

madcapnmckay
madcapnmckay

Reputation: 15984

Your problem was quite simple. Your constructors for File, Claim etc were great except you were never using them. You were pushing raw json into your collections, for example.

this.files.push(file2);

Should really be

this.files.push(new File(file2));

You also weren't initializing your objects properly. Here is a fiddle demonstrating how to do it with the mapping plugin.

http://jsfiddle.net/madcapnmckay/a8mZY/1/

Hope this helps.

Upvotes: 1

Related Questions