Knockout.js mapping JSON object to Javascript Object

DiederikTiemstra picture DiederikTiemstra · Jan 10, 2012 · Viewed 10.1k times · Source

I have a problem mapping a Json object recieved from the server into a predefined Javascript-object which contains all the necessary functions which are used in the bindings

Javascript code is the following

function Person(FirstName, LastName, Friends) {
    var self = this;
    self.FirstName = ko.observable(FirstName);
    self.LastName = ko.observable(LastName);
    self.FullName = ko.computed(function () {
        return self.FirstName() + ' ' + self.LastName();
    })
    self.Friends = ko.observableArray(Friends);
    self.AddFriend = function () {
        self.Friends.push(new Person('new', 'friend'));
    };
    self.DeleteFriend = function (friend) {
        self.Friends.remove(friend);
    };      
}

var viewModel = new Person();

$(document).ready(function () {
    $.ajax({
        url: 'Home/GetPerson',
        dataType: 'json',
        type: 'GET',
        success: function (jsonResult) {
            viewModel = ko.mapping.fromJS(jsonResult);
            ko.applyBindings(viewModel);
        }
    });
});

HTML:

<p>First name: <input data-bind="value: FirstName" /></p>
<p>Last name: <input data-bind="value: LastName" /></p>
<p>Full name: <span data-bind="text: FullName" /></p>
<p>#Friends: <span data-bind="text: Friends().length" /></p>
@*Allow maximum of 5 friends*@
<p><button data-bind="click: AddFriend, text:'add new friend', enable:Friends().length < 5" /></p>
<br>
@*define how friends should be rendered*@
<table data-bind="foreach: Friends">
    <tr>
        <td>First name: <input data-bind="value: FirstName" /></td>
        <td>Last name: <input data-bind="value: LastName" /></td>
        <td>Full name: <span data-bind="text: FullName" /></td> 
        <td><button data-bind="click: function(){ $parent.DeleteFriend($data) }, text:'delete'"/></td> 
    </tr>
</table>

My ServerSide MVC code for getting initial data looks like:

    public ActionResult GetPerson()
    {
         Person person = new Person{FirstName = "My", LastName="Name",
                        Friends = new List<Person>
                        {
                             new Person{FirstName = "Friend", LastName="Number1"},
                             new Person{FirstName = "Friend", LastName="Number2"}
                        }
        };
        return Json(person, JsonRequestBehavior.AllowGet);
    }

I am trying to use the mapping plugin to load the Json into my Javascript object so everything is available for the bindings (The add-function and the computed properties on the Friend objects).

When I use the mapping plugin it does not seem to work. When using the plugin the AddFriend method is not available during the binding. Is it possible to populate the JavaScript Person object by using the mapping plugin or must everything be done manually?

Answer

RP Niemeyer picture RP Niemeyer · Jan 10, 2012

You can look at passing mapping options to the mapping plugin, specifically a create callback as described here.

Something like:

var mapping = {
    create: function(options) {
        var person = options.data,
            friends = ko.utils.arrayMap(person.Friends, function(friend) {
               return new Person(friend.FirstName, friend.LastName);   
            });

        return new Person(person.FirstName, person.LastName, friends);
    }
};

Sample: http://jsfiddle.net/rniemeyer/5EWDG/

However, in this case, you don't get a lot of power from the mapping plugin and you could just do it without by calling the Person constructor yourself and mapping the Friends array to Person objects in the constructor like: http://jsfiddle.net/rniemeyer/mewZD/

One other note from your HTML: Make sure that for elements that contain content, that you specify the starting and ending tags (span and button were not this way in your code). KO will have problems if you do not specify them properly.