Domanda

I am using the knockout mapping plugin to add a computed property to an item in a observable array. However, this computed property relies on a different property in my viewmodel.

How do I access a viewmodel property when creating an observable during mapping?

Please note that I cannot use options.parent, because the property is further up in the viewModel.

I also am unable to change the viewmodel, because it is generated server side.

Edit:

Here's a JSFiddle that shows the issue. The line that is commented out is what I need to get working.

http://jsfiddle.net/g46mt/2/

This is what I have now, but it is obviously throwing an error:

var mapping = {
    'Collection': {
        create: function(options) {
            var model = ko.mapping.fromJS(options.data);
            model.Total = ko.computed(function() {
                var result = this.Price() * viewModel.Count(); // :(
                return result;
            }, model);

            return model;
        }
    }
};

var json = { ... large json object ... };
var viewModel = ko.mapping.fromJS(json, mapping);
È stato utile?

Soluzione

One possible solution would be to use the {deferEvaluation: true} option. So your computed function will be only evaluated when the viewModel is ready and your Total property is actually used:

var mapping = {
    'Collection': {
        create: function(options) {
            var model = ko.mapping.fromJS(options.data);
            model.Total = ko.computed(function() {
                var result = this.Price() * viewModel.Count();
                return result;
            }, model, {deferEvaluation: true});
            return model;
        }
    }
};

Demo JSFiddle.

However with this approach you are tightly coupling your view model name with your mapping options. Because if you ever change var viewModel = ko.mapping.fromJS(json, mapping); and name your viewModel differently then you have to also update the mapping configuration.

Because there is no way to walk the "parent" chain in the mapping config probably for this scenario the mapping plugin is not the best solution...

Here is an alternative approach using handwritten viewmodels and passing down the "root" where is it needed:

var MainViewModel = function (json) {    
    ko.mapping.fromJS(json, { 'ignore': ["Foo"] }, this); //map the rest
    this.Foo = new FooViewModel(json.Foo, this);
}

var FooViewModel = function (json, root) {
    ko.mapping.fromJS(json, { 'ignore': ["Bar"] }, this); //map the rest
    this.Bar = new BarViewModel(json.Bar, root);
}

var BarViewModel = function (json, root) {
    ko.mapping.fromJS(json, { 'ignore': ["Collection"] }, this); //map the rest
    this.Collection = ko.mapping.fromJS(json.Collection, {
       create: function(options) {
            var model = ko.mapping.fromJS(options.data);
            model.Total = ko.computed(function() {
                var result = this.Price() * root.Count();
                return result;
            }, model);

            return model;

       }});
}

Demo JSFiddle.

Altri suggerimenti

With the mapping plugin, it is possible to map from multiple sources. I think you could just do

var viewModel = ko.mapping.fromJS(json);
viewModel = ko.mapping.fromJS(json, mapping, viewModel),

After the first time mapping, you have your Collection with each Price as an observable. After the 2nd mapping, you'd have Total as well as an observable.

demo i jsfiddle

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top