0

I get a specific JSON from a server and want to be able to add/edit/delete items in nested arrays (variant lists, variants and columns) but I can't figure out how to do that with knockout.js.

I know that I need to change the properties in that JSON object to observables and I do it with the mapping plugin like shown under "Binding" and all values has been bind correctly - but if I change a value in an input field, the model/view is not updated automatically.

Why? Am I missing something?

So are nested arrays supported native by knockout.js without the need to write own code? How can I get this JSON to a full working knockout.js view model?

I use the current available versions (knockout: v2.1.0, mapping plugin: v2.3.2).

JSON

{
    "VariantList": [
        {
            "ColumnCount": 1,
            "Variants": [
                {
                    "Title": "One column 100%",
                    "Columns": [
                        "100 %"
                    ]
                }
            ]
        },
        {
            "ColumnCount": 2,
            "Variants": [
                {
                    "Title": "Two columns 50%/50%",
                    "Columns": [
                        "50%",
                        "50%"
                    ]
                },
                {
                    "Title": "Two columns 75%/25%",
                    "Columns": [
                        "75%",
                        "25%"
                    ]
                }
            ]
        }
    ]
}

HTML

<div data-bind="foreach: VariantList">
    <h2 data-bind="text: ColumnCount"></h2>
    <div data-bind="foreach: Variants">
        <h3 data-bind="text: Title"></h3>
        <table style="width:500px">
            <tr>
                <!-- ko foreach: Columns -->
                <th><input data-bind="value: $data"/></th>
                <!-- /ko -->
            </tr>
            <tr>
                <!-- ko foreach: Columns -->
                <td data-bind="style: {width:$data}, text:$data"></td>
                <!-- /ko -->
            </tr>
        </table>
   </div>
</div>

Binding

var viewModel;
$(function(){
   viewModel = ko.mapping.fromJS(myJson);
   ko.applyBindings(viewModel);
});

1 Answer 1

2

The issue is that the mapping plugin does not turn primitive values in an array into observables, by default. Even it it did, by the time that you bind $data against your input, you would have the unwrapped value of it rather than the observable.

The easiest way to make this work is to structure your data something like:

var data = {
    "VariantList": [
        {
            "ColumnCount": 1,
            "Variants": [
                {
                    "Title": "One column 100%",
                    "Columns": [
                        { value: "100 %" }
                    ]
                }
            ]
        },
        {
            "ColumnCount": 2,
            "Variants": [
                {
                    "Title": "Two columns 50%/50%",
                    "Columns": [
                        { value: "50%" },
                        { value: "50%" }
                    ]
                },
                {
                    "Title": "Two columns 75%/25%",
                    "Columns": [
                        { value: "75%" },
                        { value: "25%" }
                    ]
                }
            ]
        }
    ]
};

Then you would bind against value in your loop on Columns. Here is a sample: http://jsfiddle.net/rniemeyer/MCnMX/

If you are not able to pull your data in this structure, then you can consider using the mapping options, to turn it into a structure like this. Here is a sample: http://jsfiddle.net/rniemeyer/sH3r2/

var mappingOptions = {
    Columns: {
        create: function(options) {
            return { value: ko.observable(options.data) };  
        }
    }        
};

var viewModel = ko.mapping.fromJS(data, mappingOptions);

If you need to send your JSON back to the server in the same format that you received it, then there are a few options. I kind of like to do it like this: http://www.knockmeout.net/2011/04/controlling-how-object-is-converted-to.html. Here is a sample with your data: http://jsfiddle.net/rniemeyer/Eed2R/

var Value = function(val) {
    this.value = ko.observable(val);  
};

Value.prototype.toJSON = function() {
    return ko.utils.unwrapObservable(this.value);  
};

var mappingOptions = {
    Columns: {
        create: function(options) {
            return new Value(options.data);
        }
    }        
};

var viewModel = ko.mapping.fromJS(data, mappingOptions);
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for this excellent answer! I think to change the column to an object is the correct solution so I changed my project like that and now it works fine! I'm still struggling with using classes and initialising by json object but I think I will post another question if I can't get it done by myself. Knockout is great! I just need to invest a bit more time to learn it. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.