Mapping JSON with knockout fails to populate type defined object properties
Asked Answered
L

2

1

I'm trying to map JSON data using the knockout.mapping plugin, however the heirarcical JSON data fails to populate my object properties correctly, the top level loads fine but not the child 'RootTarget' data?

What am I doing wrong?

Knockout Javascript

var Query = function(json)
{
    this.ID = ko.observable(0);
    this.Name = ko.observable();
    this.RootTargetID = ko.observable();
    this.RootTarget = ko.observable();

    var mapping = {
        'RootTarget': {
            create: function (args) {
                return new QueryTarget(args.data, null);
            }
        }
    };

    ko.mapping.fromJS(json, mapping, this);
}


var QueryTarget = function(json, parent)
{
    this.ID = ko.observable(0);
    this.Name = ko.observable();
    this.ParentID = ko.observable(0);
    this.Parent = ko.observable(parent);
    this.FilterID = ko.observable(0);

    var mapping = {
        'ignore': ["Parent"]
    };

    ko.mapping.fromJS(json, mapping, this);
}

var QueryModuleViewModel = function()
{
    var json = {
        "ID": 2,
        "Name": "Northwind 2",
        "RootTargetID": 2,
        "RootTarget": {
            "ID": 2,
            "Name": "Customers",
            "ParentID": null,
            "FilterID": 2,
            "Parent": null
        }
    };

    this.QueryObj = new Query(json);
}

window.onload = function () {
    ko.applyBindings(new QueryModuleViewModel());
};

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript Knockout Mapping Query Test</title>
    <link rel="stylesheet" href="app.css" type="text/css" />

    <script src="Scripts/jquery-2.0.2.js" type="text/javascript"></script>
    <script src="Scripts/knockout-2.2.1.debug.js" type="text/javascript"></script>
    <script src="Scripts/knockout.mapping-latest.debug.js" type="text/javascript"></script>
    <script src="my_js_query_test.js"></script>

</head>
<body>
    <h1>TypeScript Knockout Mapping Query Test</h1>
    <div data-bind="with: QueryObj">
        <span data-bind="blah: console.log($context)"></span>

        <p>Query Name: <input data-bind="value: Name" /></p>

        <hr />
        <p>Quick test of RootTarget Values</p>
        <p>RootTarget.ID: <input data-bind="value: RootTarget.ID" /></p>
        <p>RootTarget.Name: <input data-bind="value: RootTarget.Name" /></p>
    </div>
</body>
</html>
Lockwood answered 12/7, 2013 at 10:12 Comment(2)
can you plz post jsfiddle example ?just copy your json data and test with it as static dataLammergeier
Sorry, should have done that in the frist place. Fortunatly @nevesv has posted a fiddle fix in his answer below.Lockwood
D
2

Because your RootTarget is declared as an ko.observable which is a function so you need to call it with empty args () to get its value and access the stored object.

So you just need to change your bindings and add the missing ():

<p>RootTarget.ID: <input data-bind="value: RootTarget().ID" /></p>
<p>RootTarget.Name: <input data-bind="value: RootTarget().Name" /></p>

Demo JSFiddle.

Or you can use here the with binding

<p>Quick test of RootTarget Values</p>
<!-- ko with: RootTarget -->
   <p>RootTarget.ID: <input data-bind="value: ID" /></p>
   <p>RootTarget.Name: <input data-bind="value: Name" /></p>
<!-- /ko -->

Demo JSFiddle.

It has some nice advantages:

  • you don't have to repeat RootTarget
  • the with automatically unwraps the observables so you can just write with: RootTarget, no parens needed
  • it works for the case when the RootTarget value is null or undified so it hides the inputs while your original solution RootTarget().ID would throw a null reference exception.
Dormitory answered 12/7, 2013 at 10:23 Comment(1)
I am such an idiot, lol, I knew it was something simple but just couldn't see the obvious, thank you sir :)Lockwood
M
0

You need brackets on your roottarget mappings as you need to evaluate the observable before you can get it's properties.

<p>RootTarget.ID: <input data-bind="value: RootTarget().ID" /></p>

Alternatively, RootTarget doesn't actually need to be an observable, only its properties do, so if you remove the line below it'll automatically be created as a normal object and your bindings will work as they are.

this.RootTarget = ko.observable;
Melbamelborn answered 12/7, 2013 at 10:31 Comment(1)
It was converted from a TypeScript class definition for this question, but I guess the same rules will apply there too, good tip! Thank you :)Lockwood

© 2022 - 2024 — McMap. All rights reserved.