I'm gonna try to make a simplified example of a problem that I have. I think that I'm very close to a solution but I need your help with the finishing touch.
Let's say that I have a viewmodel that looks like this:
function viewModel(){
var self = this;
self.foo = ko.observable();
self.bars = ko.observableArray();
}
function bar(data){
var self = this;
self.baz = ko.observable();
ko.mapping.fromJS(data, mapperSettings, self);
}
I use the ko.mapping plugin to map some data from the server:
var mapperSettings = {
bars: {
create: function(options) {
return new bar(options.data);
}
}
};
var dummyData = {
foo: '1',
bars: [{ baz: 1 }, { baz: 2 }]
};
var vm = new viewModel();
ko.mapping.fromJS(dummyData, mapperSettings, vm);
So far everything is fine.
Now, I would like to augument the view model and its items with some extra computed properties. I've managed to add it to the view model successfully (something_computed
below), but I can't manage to add a computed property to a list item - computed
property on a bar
item below:
var settings = {
create: function(options) {
var model = ko.mapping.fromJS(options.data, {
bars: {
create: function (options) {
// why is this callback called 4 times and not 2?
var self = options.data;
self.computed = ko.computed(function(){
return this.baz() + ' comp';
}, self);
return self;
}
}
});
model.something_computed = ko.computed(function(){
return this.foo() + '...';
}, model);
return model;
}
};
var newViewModel = ko.mapping.fromJS(vm, settings);
ko.applyBindings(newViewModel);
I have tweaked your fiddle and got it working as I think you want it - it is here:
https://jsfiddle.net/4s6jsLx1/10/
The main thing I've done is to implement the pattern described below in order to allow you to easily and cleanly define nested view models each with computeds, without your code becoming tied in knots (see note at bottom about create being called 4 times) --
Firstly, create some dummy data, eg
data = {foo: 1}
Then, create a view model definition for a view model that will hold this dummy data. Define your computed here. You don't normally need to define an observable for foo
because the mapping plugin will create that for you, but because you reference it from the computed, then in this case, you do need to define it:
function viewModel() {
var self = this
self.foo = ko.observable()
self.computed_foo = ko.computed(function() {
return self.foo() + " comp"
})
}
Then instantiate this as a blank view model, to get an instance that has the computed:
blank_vm = new viewModel()
Finally, use ko.mapping to fill it in with the data. This example supplies {}
as a blank settings object, but in the fiddle I use your proper settings object to create the bars:
vm = ko.mapping.fromJS(data, {}, blank_vm)
vm
now has two properties, foo
(where foo() == 1
) and computed_foo
(where computed_foo() == "1_comp"
Regarding create being called 4 times, if you replace your console.log with:
console.log('create', options.data);
then you will see on the 3rd and 4th time it is called, the data passed in is garbage. I did not spend much time working out exactly where it came from but broadly speaking I think your code became a bit tied in knots in terms of how the mappings and computeds were interacting. My solution has removed this create method entirely by following the pattern outlined above, which is intended to keep the creation of computeds separate from the mapping process. Hopefully it does what you need!