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