Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / Javascript

Knockout JS mappings

Rate me:
Please Sign up or sign in to vote.
4.85/5 (9 votes)
15 Jan 2013CPOL4 min read 53.6K   17   8
Knockout JS mappings.

Click here for the original article. 

Knockout is a JavaScript library for creating MVVM JavaScript libraries. In a previous post I already showed some of the cool features of Knockout. 

http://marcofranssen.nl/2011/09/13/knockout-that-cascading-dropdown/

If you want to spike your knowledge on Knockout a little more first, please visit Knockout’s documentation.

In this article I want to zoom in on the Knockout mapping plugin. The Knockout mapping plugin enables you to easy map your JSON object into an observable JavaScript object. So here is a short example of what you’ll be able to do with it. So when you do an ajax request and receive a JSON result you can for example do the following.

 viewModel;
// result could look like this: "{ "personId": 1, "firstName": "Marco", "lastName": "Franssen", "age": 26, "webpage": "http://marcofranssen.nl", "twitter": "@marcofranssen" }"$.ajax({
    url: 'http://somewebpage.net/getPerson'
    type: 'GET',
    dataType: 'JSON',
    success: function (result) {
         data = JSON.parse(result);
        viewModel = ko.mapping.fromJS(data);
        ko.applyBindings(viewModel, $('#person').get(<span class="mi">0));
    },
    error: function (result) {
        //handle the error, left for brevity
    }
});

So what we are doing is making a request to some url which returns us a person JSON object. Then we parse the JSON to a regular JavaScript object, followed by mapping it into our view model. Last thing we do is binding the view model to the DOM nodes in our person DOM node (could be a div element or whatever). This creates the same view model object as we would do it by hand, like this.

 data = JSON.parse(result);
viewModel = {
    personId: ko.observable(data.firstName),
    firstName: ko.observable(data.firstName),
    lastName: ko.observable(data.lastName),
    age: ko.observable(data.age),
    webpage: ko.observable(data.webpage),
    twitter: ko.observable(data.twitter)
};

So the mapping plugin easily maps our JSON into an observable view model. But what if we want to skip some properties of our JSON or just don’t want to wrap them into an observable. Well you definitely don’t have to fall back on writing the object completely by hand. We can simply configure our mapping in an object and pass that into our call to ko.mapping.fromJS.

 mapping = {
    'ignore': ['twitter', 'webpage'],
    'copy': ['age', 'personId'],
    'lastName': {
        'create': function (options) {
            return ko.observable(options.data.toUpperCase());
        }
    }
};

viewModel = ko.mapping.fromJS(data, mapping);

By passing in the following mapping to the mapping plugin we tell the mapping plugin to ignore the twitter and webpage properties. We only want to copy the value of the personId and age property (Not wrap it in a ko.observable) and we configured our lastName property to be transformed to upper-case before putting it in an observable. When we would have done this by hand our code could have looked like this.

viewModel = {
    personId: data.personId,
    firstName: ko.observable(data.firstNametoUpperCase()),
    lastName: ko.observable(data.lastName),
    age: data.age,
};

Now you’re maybe thinking why would I skip the observable wrapping for some properties…. The best reason to do this is when you know the property won’t change (by user or update from server). Observables are quite expensive when you have a lot of them and require more memory. So when you have for example a big collection of persons in a table you could skip the observables for some properties to save memory and gain a small performance boost.

The mapping plugin can also be used when you get a collection of persons from the server. What you basically do is creating a view model and add the persons to the observable collection of your view model.

 viewModel = {
    persons: ko.observableArray(),
};

 persons = JSON.parse(jsonPersonArray);
for (person in persons) {

    viewModel.persons.push(person);
}

//Or even better performance wise (persons observableArray gets updated only once)
viewModel.persons.push.apply(viewModel.persons, persons);

With above example the person object is just a plain JavaScript object without observables. We can change that by creating another view model for each person. Probably you also want to check if the person isn’t already in the observable array and so on. So imagine what amount of code you probably are going to put into the for loop. Luckily we have the mapping plugin.

First of all I will define a constructor / view model for my person objects. We just use the mapping defined before and adding a computed property to our view model by hand. You can of course add as many properties as you want by hand, to extend your person object with more functionality.

function PersonViewModel(data) {
     personMapping = {
        'ignore': ['twitter', 'webpage'],
        'copy': ['age'],
        'lastName': {
            'create': function (options) {
               return ko.observable(options.data.toUpperCase());
            }
        }
    };

    ko.mapping.fromJS(data, personMapping, this);

    this.fullName = ko.computed(function () {
        return this.firstName() + ' ' + this.lastName();

    }, this);

}

I also create a constructor for my root view model. This way I can also encapsulate the mapping logic and for example create multiple instances of it. Lets imagine we want to have a company view model containing employees. So we get some JSON from the server containing an array of persons called employees and some other properties. The company’s name we will convert to uppercase and we will ignore the companies address and website properties.

function CompanyViewModel (data) {
     companyMapping = {
        'ignore': ['address', 'website'],
        'name': {
            'create': function (options) {
                return ko.observable(options.data.toUpperCase());
            }
        },
        'employees': {
            key: 'peronsId',
            create: function (options) {
                return new PersonViewModel(options.data);
            }
        }
    };
    ko.mapping.fromJS(data, companyMapping, this);
}

Note we create a new person view model for each of the persons in our employees array. The PersonViewModel is responsible for its own mapping (defined in our constructor). We also defined a key for our person object so Knockout knows what makes a person unique. Also note that we didn’t customized the way personId is mapped within our person view model.

So everything is not specified in the mapping object will by default be wrapped in an observable.

So when we do a request we can instantiate our view model like this.

 comany;
$.ajax({
    url: 'http://companyindex.org/getcompanydata/4327/',
    type: 'GET',
    dataType: 'JSON',
    success: function (result) {
         data = JSON.parse(result);
        company = new CompanyViewModel(data);
        ko.applyBindings(company, $('#company').get(<span class="mi">0));
    },
    error: function (result) {
        //left for brevity
    }
});

We make a call to some url returning the information of a company in JSON format. Create a new CompanyViewModel and apply the bindings. All the mapping logic is in the constructors. See this jsFiddle for a complete example.

Thanks again for reading my article. Please write a comment and share it with your friends and colleagues.

This article was originally posted at http://marcofranssen.nl?p=321

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Atos
Netherlands Netherlands
I am a .NET Software Developer at Atos International. Architecture, CQRS, DDD, C#, ASP.NET, MVC3, HTML5, Jquery, WP7, WPF, ncqrs, Node.js

Comments and Discussions

 
QuestionGood example Pin
Varun Gb23-Oct-14 14:36
Varun Gb23-Oct-14 14:36 
QuestionKo Mapping and Jasmine Pin
Member 1087719718-Aug-14 19:44
Member 1087719718-Aug-14 19:44 
AnswerRe: Ko Mapping and Jasmine Pin
marcofranssen2-Oct-14 0:31
professionalmarcofranssen2-Oct-14 0:31 
Generalupdated jsfiddle Pin
sanjozko13-Jan-14 2:10
sanjozko13-Jan-14 2:10 
Questionnice article, one question Pin
jahmani17-Dec-12 8:15
jahmani17-Dec-12 8:15 
AnswerRe: nice article, one question Pin
marcofranssen17-Dec-12 22:44
professionalmarcofranssen17-Dec-12 22:44 
AnswerRe: nice article, one question Pin
marcofranssen15-Jan-13 3:29
professionalmarcofranssen15-Jan-13 3:29 
You should add a function to your company viewModel, addNewEmployee.

The function wraps this logic

CompanyViewmodel.employees.push({ firstName: '', lastName: '', etc.....})

So you have to provide a prototype in order to make the mapping do the job

everytime you want to add an new employee just call your function.
QuestionGreat article Pin
jeffb4214-Dec-12 7:13
jeffb4214-Dec-12 7:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.