I am applying JSON data to my ViewModel using ko.mapping.fromJS
. This JSON data has nested objects within it, like so:
var data = {
item : "#1234",
description: "This is item #1",
moreinfo: {
condition: "Good"
}
}
When I try to re-map new data to the object it works fine, updating all the values. However, if I remap the value of moreinfo
to null
because this item doesn't have moreInfo, like so:
var data2 = {
item : "#4567",
description: "This is item #2",
moreinfo:null
}
it doesn't update the DOM but instead keeps the previous value condition:"Good"
. If I then update the value one more time to data that has moreinfo
, like so:
var data3 = {
item : "#7890",
description: "This is item #3",
moreinfo: {
condition: "Bad"
}
}
it still updates the item and description, but still it doesn't update the DOM but instead keeps the value condition:"Good"
.
Am I using mapping incorrectly or can you just not allow the value to become null
?
Javascript:
var viewModel;
$("#button1").on("click", function(){
viewModel = ko.mapping.fromJS(data);
ko.applyBindings(viewModel, $("itemWrapper")[0]);
});
$("#button2").on("click", function(){
ko.mapping.fromJS(data2, {}, viewModel);
});
$("#button3").on("click", function(){
ko.mapping.fromJS(data3, {}, viewModel);
});
HTML:
<div id="itemWrapper">
<p data-bind="text: item"></p>
<p data-bind="text: description"></p>
<p data-bind="text: moreinfo.condition"></p>
</div>
<button id="button1">
Bind Data #1
</button>
<button id="button2">
Bind Data #2
</button>
<button id="button3">
Bind Data #3
</button>
JSfiddle: https://jsfiddle.net/hrgx5f1y/
If you applyBindings with Button #1, then map the null with #2, then map new values with #3 you will see the issue I describe.
If you applyBindings with Button #1, then map new values with #3, then map new values with #4 (none of these are null) it works perfectly.
The way that Knockout binding handlers work is that they hook up to an observable, and they respond when that observable changes. It does not bind to your binding expression but to your observable object So your binding:
<p data-bind="text: moreinfo.condition"></p>
...takes the condition
property of moreinfo
, which is an observable object and subscribes to it. When you do this:
var data4 = {
item : "#0000",
description: "This is item #4",
moreinfo: {
condition: "Meh"
}
}
ko.mapping.fromJS(data4, {}, viewModel);
...it works because Knockout can tie the structure of data4
to your view model and update that exact same observable object with the new value 'Meh'
.
If you do this instead:
var data2 = {
item : "#4567",
description: "This is item #2",
moreinfo:null
}
$("#button2").on("click", function(){
ko.mapping.fromJS(data2, {}, viewModel);
});
...it is not updating that observable, but rather is updating the moreinfo
property so it's null. Since a binding is to an observable object, not an expression, even if you update the view model so moreinfo
isn't null anymore, the binding expression is not re-evaluated; your DOM is still bound to that same original observable.
You can work around this by having moreinfo
as an observable, binding to it, and subsequently binding to condition
; that way, if either updates, your DOM will update as expected. For example:
<!-- ko with:moreinfo -->
<p data-bind="text: condition"></p>
<!-- /ko -->