I have what is likely a simple Knockout question but I'm a plete beginner with it. I was tossed this page of code that someone else has worked on but never finished.
When this page first loads, the data is retrieved and the main grid loads properly. The problem es in when I attempt to auto-select the first record in the results so that a detail list gets filled out below the grid.
When that happens, I receive the following message.
Uncaught TypeError: Unable to process binding "text: function (){return selected.RequestLog.Timestamp }" , Message: Cannot read property 'Timestamp' of undefined
Here is the code snippets with which I'm working. The data ing back is from Entity Framework.
var siteLogModel = function () {
var self = this;
self.errorList = ko.observableArray([]);
self.selected = ko.observable();
self.updateErrorList = function (page) {
jQuery.ajax({
type: "POST",
url: "/Admin/ErrorPage",
data: { pageNum: page },
success: function (result) {
self.errorList(result);
self.selected(result[0]);
// Since we have success, add the click handler so we can get the details about a row by id.
//addRowHandlers();
},
error: function (result) {
jQuery("#status").text = result;
}
});
};
};
This is the actual binding that tries to happen after the data is loaded. RequestLog does not seem to exist at binding time, even though it does seem to be ok if I set a breakpoint in the above function on the line self.selected(result[0]).
I think this is a scope problem but I can't for the life of me think of how best to fix it. Any help would be appreciated.
<div class="param">
<span>Time</span>
<label data-bind="text: selected.RequestLog.Timestamp"></label>
</div>
UPDATE: Here is the document ready portion.
jQuery(document).ready(function () {
var vm = new siteLogModel();
vm.updateErrorList(0);
ko.applyBindings(vm);
});
I have what is likely a simple Knockout question but I'm a plete beginner with it. I was tossed this page of code that someone else has worked on but never finished.
When this page first loads, the data is retrieved and the main grid loads properly. The problem es in when I attempt to auto-select the first record in the results so that a detail list gets filled out below the grid.
When that happens, I receive the following message.
Uncaught TypeError: Unable to process binding "text: function (){return selected.RequestLog.Timestamp }" , Message: Cannot read property 'Timestamp' of undefined
Here is the code snippets with which I'm working. The data ing back is from Entity Framework.
var siteLogModel = function () {
var self = this;
self.errorList = ko.observableArray([]);
self.selected = ko.observable();
self.updateErrorList = function (page) {
jQuery.ajax({
type: "POST",
url: "/Admin/ErrorPage",
data: { pageNum: page },
success: function (result) {
self.errorList(result);
self.selected(result[0]);
// Since we have success, add the click handler so we can get the details about a row by id.
//addRowHandlers();
},
error: function (result) {
jQuery("#status").text = result;
}
});
};
};
This is the actual binding that tries to happen after the data is loaded. RequestLog does not seem to exist at binding time, even though it does seem to be ok if I set a breakpoint in the above function on the line self.selected(result[0]).
I think this is a scope problem but I can't for the life of me think of how best to fix it. Any help would be appreciated.
<div class="param">
<span>Time</span>
<label data-bind="text: selected.RequestLog.Timestamp"></label>
</div>
UPDATE: Here is the document ready portion.
jQuery(document).ready(function () {
var vm = new siteLogModel();
vm.updateErrorList(0);
ko.applyBindings(vm);
});
Share
Improve this question
edited Jan 22, 2014 at 20:31
Rob Horton
asked Jan 22, 2014 at 18:41
Rob HortonRob Horton
8253 gold badges10 silver badges28 bronze badges
1
- What does your result object look like? Also, there may be an issue with how you are using observableArray. There is no need to pass the constructor an empty array (though it probably won't hurt) and you can use self.errorList.push(result) to add values. – bigbangtheorem Commented Jan 22, 2014 at 18:47
1 Answer
Reset to default 13Your selected
observable does not have a .RequestLog
property at the time ko is evaluating the binding expression. That error is ing 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 puted instead:
<div class="param">
<span>Time</span>
<label data-bind="text: selectedRequestLogTimestamp"></label>
</div>
self.selectedRequestLogTimestamp = ko.puted(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 ment #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>