Map JSON data to Knockout observableArray with specific view model type
Asked Answered
S

1

41

Is there a way to map a JSON data object to an observable array and then in turn have each item of the observable array be initialized into a specific type of view model?

I've looked at all of knockout's documentation along with the knockout and mapping examples here and I can't find any answer that works for what I'm after.

So, I have the following JSON data:

    var data = {
    state : {
        name : 'SD',
        cities : [{
            name : 'Sioux Falls',
            streets : [{
                number : 1
            }, {
                number : 3
            }]
        }, {
            name : 'Rapid City',
            streets : [{
                number : 2
            }, {
                number : 4
            }]
        }]
    }
};

And I have the following view models:

var StateViewModel = function(){
    this.name = ko.observable();
    this.cities = ko.observableArray([new CityViewModel()]);
}

var CityViewModel = function(){
    this.name = ko.observable();
    this.streets = ko.observableArray([new StreetViewModel()]);
}

var StreetViewModel = function(){
    this.number = ko.observable();
}

Is it possible, with the given data structure and using knockout's mapping plugin, to have the resulting StateViewModel contain an observableArray populated with 2 CityViewModels, and each CityViewModel containing an observableArray populated with 2 StreetViewModels?

Currently using the mapping plugin I'm able to get it to map to a StateViewModel, but the 'cities' and 'streets' collections are populated with generic objects instead of instances of my City and Street view models.

They end up with the correct observable properties and values on them, they're just not instances of my view models, which is what I'm after.

Shockproof answered 30/3, 2012 at 23:18 Comment(0)
S
69

Check this http://jsfiddle.net/pTEbA/268/

Object.prototype.getName = function() { 
   var funcNameRegex = /function (.{1,})\(/;
   var results = (funcNameRegex).exec((this).constructor.toString());
   return (results && results.length > 1) ? results[1] : "";
};

function StateViewModel(data){
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

function CityViewModel(data) {
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

function StreetViewModel(data) {
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

var mapping = {
    'cities': {
        create: function(options) {
            return new CityViewModel(options.data);
        }
    },
    'streets': {
        create: function(options) {
            return new StreetViewModel(options.data);
        }
    }
}


var data = { state: {name:'SD', cities:[{name:'Sioux Falls',streets:[{number:1},{number:3}]},
                                        {name:'Rapid City',streets:[{number:2},{number:4}]}]}};

var vm = new StateViewModel(data.state)
console.log(vm);
console.log(vm.getName());
console.log(vm.cities());
console.log(vm.cities()[0].getName());
console.log(vm.cities()[0].streets());
console.log(vm.cities()[0].streets()[0].getName());
​
Straley answered 31/3, 2012 at 0:23 Comment(4)
I did have one follow up question though: when creating cities, it looks like the create mapping actually returns a single object of type CityViewModel, and is passed a city object. What would you do if you wanted to have a collection type like CitiesViewModel with its own collection methods?Robot
Looks great? But what does the this.$type = '..Model do in the constructors? I can't find anything about a $type property in the online Knockout or Knockout.mapping documentation.Walkling
$type is not actually necessary... just to demonstrate that constructor was called.Straley
Thanks. This example should be in the official Knockout Documentation.Gig

© 2022 - 2024 — McMap. All rights reserved.