Knockout.js mapping JSON object to Javascript Object
Asked Answered
S

1

9

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?

Signora answered 10/1, 2012 at 15:14 Comment(0)
P
11

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.

Prevaricator answered 10/1, 2012 at 16:30 Comment(4)
Thanks a lot! Your answer made it quite clear how the mapping works.Signora
Ryan, I've been wondering about the utility of the mapping plugin once you've started building your own constructors (like your "you don't get a lot of power from the mapping plugin" comment). Have I gone down a wrong path when I start using a constructor? If I don't use a constructor, I don't know how to best handle appending the mapped data with user entered data. With a constructor I can handle the user input, check for errors, security, etc. I'm just getting going with knockout and want to go the right way. Thoughts?Patriciate
If you want the best control, then I feel that doing it yourself with constructors is the best approach. The create callbacks of the mapping plugin do let you add your own properties at various levels, but it never feels as clean to me. The mapping plugin is great when you just want to turn a structure into observables without writing much real client-sie code.Prevaricator
FYI that I just found out that the knockout tutorials also recommend manual mapping to your own objects for more control (see Step 2): learn.knockoutjs.com/#/?tutorial=loadingsavingPatriciate

© 2022 - 2024 — McMap. All rights reserved.