Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Knockout JS mappings

4.85/5 (9 votes)
15 Jan 2013CPOL4 min read 54.6K  
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.

License

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