Knockoutjs - stringify to handling observables and custom events
- by Renso
Goal:
Once you viewmodel has been built and populated with data, at some point it goal of it all is to persist the data to the database (or some other media). Regardless of where you want to save it, your client-side viewmodel needs to be converted to a JSON string and sent back to the server.
Environment considerations:
jQuery 1.4.3+
Knockoutjs version 1.1.2
How to:
So let’s set the stage, you are using Knockoutjs and you have a viewmodel with some Knockout dependencies. You want to make sure it is in the proper JSON format and via ajax post it to the server for persistence.
First order of business is to deal with the viewmodel (JSON) object. To most the JSON stringifier sounds familiar. The JSON stringifier converts JavaScript data structures into JSON text. JSON does not support cyclic data structures, so be careful to not give cyclical structures to the JSON stringifier. You may ask, is this the best way to do it? What about those observables and other Knockout properties that I don’t want to persist or want their actual value persisted and not their function, etc.
Not sure if you were aware, but KO already has a method; ko.utils.stringifyJson() - it's mostly just a wrapper around JSON.stringify. (which is native in some browsers, and can be made available by referencing json2.js in others). What does it do that the regular stringify does not is that it automatically converts observable, dependentObservable, or observableArray to their underlying value to JSON.
Hold on! There is a new feature in this version of Knockout, the ko.toJSON. It is part of the core library and it will clone the view model’s object graph, so you don’t mess it up after you have stringified it and unwrap all its observables. It's smart enough to avoid reference cycles. Since you are using the MVVM pattern it would assume you are not trying to reference DOM nodes from your view.
Wait a minute. I can already see this info on the http://knockoutjs.com/examples/contactsEditor.html website, why mention it all here? First of this is a much nicer blog, no orange ? At this time, you may want to have a look at the blog and see what I am talking about. See the save event, how they stringify the view model’s contacts only? That’s cool but what if your view model is a representation of your object you want to persist, meaning it has no property that represents the json object you want to persist, it is the view model itself. The example in http://knockoutjs.com/examples/contactsEditor.html assumes you have a list of contacts you may want to persist. In the example here, you want to persist the view model itself.
The viewmodel here looks something like this:
var myViewmodel = {
accountName: ko.observable(""),
accountType: ko.observable("Active")
};
myViewmodel.isItActive = ko.dependentObservable(function () {
return myViewmodel.accountType() == "Active";
});
myViewmodel.clickToSaveMe = function() {
SaveTheAccount();
};
Here is the function in charge of saving the account:
Function SaveTheAccount() {
$.ajax({
data: ko.toJSON(viewmodel),
url: $('#ajaxSaveAccountUrl').val(),
type: "POST",
dataType: "json",
async: false,
success: function (result) {
if (result && result.Success == true) {
$('#accountMessage').html('<span class="fadeMyContainerSlowly">The account has been saved</span>').show();
FadeContainerAwaySlowly();
}
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
alert('An error occurred: ' + errorThrown);
}
}); //ajax
};
Try run this and your browser will eventually freeze up or crash. Firebug will tell you that you have a repetitive call to the first function call in your model that keeps firing infinitely. What is happening is that Knockout serializes the view model to a JSON string by traversing the object graph and firing off the functions, again-and-again. Not sure why it does that, but it does. So what is the work around:
Nullify your function calls and then post it:
var lightweightModel = viewmodel.clickToSaveMe = null;
data: ko.toJSON(lightweightModel),
So then I traced the JSON string on the server and found it having issues with primitive types. C#, by the way. So I changed ko.toJSON(model) to ko.toJS(model), and that solved my problem.
Of course you could just create a property on the viewmodel for the account itself, so you only have to serialize the property and not the entire viewmodel. If that is an option then that would be the way to go. If your view model contains other properties in the view model that you also want to post then that would not be an option and then you’ll know what to watch out for. Hope this helps.