Search code examples
asp.netasp.net-mvc-4knockout.jsknockout-mapping-plugin

Do not post SelectList with Knockout


I'm running an ASP.net MVC4 application with Knockout.

I have a generic script that posts my Knockout Forms.

I need to optimize the data sent to the server, because when i post my Knockout ViewModel, SelectList with all items are posted too!

Example Server ViewModel :

Public Class FooViewModel Public Property Bar As String Public Property Products As List(Of SelectListItem) End Class

The JS code to convert my Knockout ViewModel to JSON

var data = ko.toJSON(viewModel);

data variable contains all products items and that's not very optimized.

I found this code (which work) :

viewModel.toJSON = function () {
        var copy = ko.toJS(this);
        // remove any unneeded properties           
        delete copy.Products;            
        return copy;
    }

But I need a generic solution ... ! And here I don't see how i can make it generic ...

A quick and dirty solution would be to add a suffix on every array properties like "_NoPost" and then loop and delete every property that has this suffix, but it smells ... bad :/

Any thoughts ?


Solution

  • The one option is to separate your form data from your lookup data like the following. This will allow you to get hold of only your form data when you need to post it to the server.

    Public Class FormViewModel
         Public Property Bar As String
    End Class
    
    Public Class FooViewModel
         Public Property FormData As FormViewModel
         Public Property Products As List(Of SelectListItem)
    End Class
    

    Which will allow you to

    var data = ko.toJSON(viewModel);
    $post(url, data.FormData, function(d){...});
    

    In your HTML you will also have to include the FormData as part of the variable i.e.

    <input data-bind="value: FormData.Bar">
    

    EDIT

    Based on your feedback you can use the following function to construct a "clean" object for you. The idea is to pass in the original JSON object as well as a mapping object which will indicate which of the properties should be excluded/left behind:

    function MapJson(obj, map) {
    
        if (obj == undefined)
            return;
    
        map = map || {};
        var ret = {};
    
        for (var prop in obj) {
    
            if (map[prop] != undefined && map[prop] == false)
                continue;
    
            if (typeof obj[prop] !== "object")
                ret[prop] = obj[prop];
            else {
                if (map.constructor == Array) {
                    ret[prop] = MapJson(obj[prop], map[0]);
                }
                else
                    ret[prop] = MapJson(obj[prop], map[prop]);
            }
    
        }
    
        return ret;
    }
    

    You can then use it like this - by setting the property's value to false it will be excluded from the data. The sample shows how to block both an array within a child object as well as an array within an array:

    var obj = {
        Name: "John Doe",
        Vehicle: {
            Details: {
                Make: "Mazda",
                Model: 2010
            },
            Registration: "ABC123",
            ServiceDates: ["01 Jan", "23 Feb", "13 March"]
        },
        WeekDays: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
        Children: [{ Name: "Mary", Age: 4, Hobbies: ["Soccer", "Chess", "Swim"] }, { Name: "Jane", Age: 2, Hobbies: ["Tennis", "Movies", "Reading"] }]
    };
    
    var map = {
        Vehicle: {
            ServiceDates: false
        },
        Children: [{
            Hobbies: false,
        }]
    };
    
    MapJson(obj, map);
    

    Hope it helps.

    EDIT 2

    Herewith a working sample based on the data you posted in your comment.

     var vm = {
        "Type":"PropertyTax",
        "Label":"d",
        "StartDate":"2015-01-01T00:00:00",
        "EndDate":"2015-12-31T00:00:00",
        "Value":0,
        "RegularizationMonth":0,
        "TotalConsumption":null,
        "UnitPrice":null,
        "Active":true,"Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"ZZZ","Value":"38"}],"ChargeProducts":[{"ProductID":"28","Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"XXX 41","Value":"8"}]}],
        "map":{"Products":false,"ChargeProducts":[{"Products":false}]}
        };
    
        var result = MapJson(vm, vm.map);
    
        console.log("Result: ", result);