Your selected
observable does not have a .RequestLog
property at the time ko is evaluating the binding expression. That error is coming from javascript, not ko (though ko wraps the exception in the error message you see). When running, selected.RequestLog === undefined
is true, and you can't invoke anything on undefined. It's like a null reference exception.
It makes sense if you are calling applyBindings
before the ajax call finishes.
One way to fix this by doing a computed instead:
<div class="param">
<span>Time</span>
<label data-bind="text: selectedRequestLogTimestamp"></label>
</div>
self.selectedRequestLogTimestamp = ko.computed(function() {
var selected = self.selected();
return selected && selected.RequestLog
? selected.RequestLog.TimeStamp
: 'Still waiting on data...';
});
With the above, nothing is ever being invoked on an undefined variable. Your label will display "Still waiting on data" until the ajax call finishes, then it will populate with the timestamp as soon as you invoke self.selected(result[0])
.
Another way to solve it is by keeping your binding the same, but by giving the selected observable an initial value. You can leave all of your html as-is, and just to this:
self.selected = ko.observable({
RequestLog: {
TimeStamp: 'Still waiting on data'
}
});
... and you will end up with the same result.
Why?
Any time you initialize an observable by doing something like self.prop = ko.observable()
, the actual value of the observable is undefined
. Try it out:
self.prop1 = ko.observable();
var prop1Value = self.prop1();
if (typeof prop1Value === 'undefined') alert('It is undefined');
else alert('this alert will not pop up unless you initialize the observable');
So to summarize what is happening:
- You initialize your selected observable with a value equal to undefined in your viewmodel.
- You call ko.applyBindings against the viewmodel.
- ko parses the data-bind attributes, and tries to bind.
- ko gets to the
text: selected.RequestLog.Timestamp
binding. - ko invokes selected(), which returns undefined.
- ko tries to invoke .RequestLog on undefined.
- ko throws an error, because undefined does not have a .RequestLog property.
All of this happens before your ajax call returns.
Reply to comment #1
Yes, you can call applyBindings after your ajax success event. However, that's typically not always what you should do. If you want to, here's one example of how it could be done:
self.updateErrorList = function (page) {
self.updateErrorPromise = jQuery.ajax({
type: "POST",
url: "/Admin/ErrorPage",
data: { pageNum: page },
success: function (result) {
self.errorList(result);
self.selected(result[0]);
},
error: function (result) {
jQuery("#status").text = result;
}
});
};
jQuery(document).ready(function () {
var vm = new siteLogModel();
vm.updateErrorList(0);
vm.updateErrorPromise.done(function() {
ko.applyBindings(vm);
});
});
Yet another way would be to go ahead and eager-bind (applyBindings before the ajax call finishes), but wrap your markup in an if binding like so:
<div class="param" data-bind="if: selected">
<span>Time</span>
<label data-bind="text: selected.RequestLog.Timestamp"></label>
</div>