I have some problems with nested view models in knockout using the mapping plugin. I'm able to recreate the problem, and I have created a fiddle for it here: Fiddle
I have stripped down the actual view and viewmodel, so don't expect the output to look nice, but it will get the message accros. This is my view:
<div data-bind="foreach: $root.selectedArmy().Units">
<div class="unitoverview">
<!-- ko foreach: UnitMembers-->
<div class="member">
<div>
<span class="name" data-bind="text: Name, click: $parent.RemoveTest"></span>
</div>
<div data-bind="foreach: test">
<span data-bind="text:$data, click: $parent.RemoveTest"></span>
</div>
<h1 data-bind="text: test2"></h1>
</div>
<!-- /ko -->
</div>
</div>
<span data-bind="click:AddUnit">CLICK TO ADD UNIT</span>
And this is my model:
var armymaker = armymaker || {};
var unitMapping = {
'UnitMembers': {
create: function (options) {
return new UnitMemberViewModel(options.data);
}
}
};
var UnitViewModel = function (unit) {
var self = this;
self.Name = ko.observable("unitname");
self.UnitDefinitionId = ko.observable(unit.Id);
ko.mapping.fromJS(unit, {}, self);
};
var UnitMemberViewModel = function (unitmemberdefinition) {
var self = this;
self.test = ko.observableArray([ko.observable('TEST'), ko.observable('TEST2')]);
self.test2 = ko.observable('TEST1');
self.RemoveTest = function () {
self.test.splice(0,1);
self.Name('BUGFACE');
self.test2('OKI!!');
};
ko.mapping.fromJS(unitmemberdefinition, {}, self);
};
var ViewModel = function () {
var self = this;
self.showLoader = ko.observable(false);
self.newArmy = ko.observable({});
self.unitToAdd = ko.observable(null);
self.selectedArmy = ko.observable({ Template: ko.observable(''), Units: ko.observableArray() });
self.AddUnit = function () {
var data = {'Name': 'My name', 'UnitMembers': [
{ 'Name': 'Unitname1' }
] };
self.unitToAdd(new UnitViewModel((ko.mapping.fromJS(data, unitMapping))));
self.selectedArmy().Units.push(self.unitToAdd());
self.unitToAdd(null);
};
};
armymaker.viewmodel = new ViewModel();
ko.applyBindings(armymaker.viewmodel);
What happens is the following:
I click the link CLICK TO ADD UNIT, and that created a UnitViewModel, and for each element in the UnitMember array it will use the UnitMemberViewModel because of the custom binder (unitMapper) that I am using.
This all seems to work fine. However in the innermost view model, I add some field to the datamodel. I have called them test that is an observableArray, and test2 that is an ordinary observable. I have also created a method called RemoveTest that is bound in the view to both the span that represent test2, and the span in the foreach that represent each element of the array test.
However when I invoke the method, the change to the observable is reflected in the view, but no changes to the observableArray is visible in the view. Check the fiddle for details.
Are there any reasons why changes to an obsArray will not be visible in the view, but changes to an ordinary observable will?
I have made some observations:
- The click event on the
observabledoes not work, only the click event on the elements on theobservableArray. - It seems that self inside the click event does not match the actual viewmodel. If I go
self.test.splice(0,1)nothing happens in the view, butself.test.spliceonly contains one element after that command. However if I traverse the base viewmodel (armymaker.viewmodel.Units()[0].UnitMembers()[0].test) is still contains two elements. - Calling splice on the traversed viewmodel (
armymaker.viewmodel.Units()[0].UnitMembers()[0].test.splice(0,1)) removes the element from the view, so it seems in some way that the element referenced by self is not the same object as what is linked inside the view. But then, why does it work for theobservablethat is not anarray?
There is probably a flaw with my model, but I can't see it so I would appreciate some help here.