Problem:
I am trying to use knockout.js with jquery templates. The problem is that an $.ajax call returns values correctly, but when I try to insert them into the corresponding ko.observableArray, I get the correct number of rows, but the values are all undefined
The problem, according to my debugging is located in a for in loop in the success callback for an $.ajax call.
It seems that if I write:
for (item in myArray)
{
alert(item.myProperty); // shows undefined
}
What am I doing wrong??!
Detailed setup follows.
Setup:
My template is:
<fieldset style="padding-top:10px;">
<legend>Associated Cost Centres</legend>
<table>
<thead>
<tr>
<th>
Cost Centre
</th>
<th></th>
</tr>
</thead>
<tbody data-bind="template: {name:'actividadesAsociadas', foreach: viewModel.costCentres}"></tbody>
</table>
</fieldset>
<script type="text/x-jquery-tmpl" id="actividadesAsociadas">
<tr>
<td data-bind="text: NameCC"></td>
<td data-bind="text: CostCentreId"></td>
<td><a href="#" data-bind="click: remove">Delete</a></td>
</tr>
</script>
My javascript is:
function costCentre(CostCentreId, IdTransactionType, NameCC, ownerViewModel) {
this.CostCentreId = ko.observable(CostCentreId);
this.IdTransactionType = ko.observable(IdTransactionType);
this.NameCC = ko.observable(NameCC);
this.remove = function() { ownerViewModel.costCentres.destroy(this) }
}
function costCentreViewModel() {
// other methods
this.costCentres = ko.observableArray([]);
var self = this;self = this;
$.ajax({url: "/[...route...]/GetCostCentres/" + @id,
dataType: 'json',
data: {},
type: 'POST',
success: function (jsonResult) {
var mappedCostCentres = $.map(jsonResult, function(item) {
return new costCentre(item.CostCentreId, item.IdTransactionType, item.Name, self)
});
for (cc in mappedCostCentres)
{
self.costCentres.push(new costCentre(cc.CostCentreId, cc.IdTransactionType, cc.NameCC, self));
}
},
error: function (result) {
$('#ErrorDisplay').show().html('<p>' + result.responseText + '</p>');
}
});
// test data
this.costCentres.push(new costCentre(55, 54, "test", this));
// other methods
};
var viewModel = new costCentreViewModel();
jQuery(document).ready(function () { ko.applyBindings(viewModel); });
The bit where the problem happens in the javascript code is:
for (cc in mappedCostCentres)
{
self.costCentres.push(new costCentre(cc.CostCentreId, cc.IdTransactionType, cc.NameCC, self));
}
The reason being that cc.CostCentreId, cc.IdTransactionType and cc.NameCC all evaluate to undefined.
I know that this is the case, because the test data is displayed correctly by the jquery.tmpl template, whereas the rows that have been brought in by the $.ajax call just display as empty tags.
jsonResult:
The jsonResult returned by the $.ajax call looks like this (this is correct):
[{"CostCentreId":5,"IdTransactionType":2,"Name":"Impuestos"},
{"CostCentreId":14,"IdTransactionType":3,"Name":"Transferencias Internas"}]
Questions:
- Having set up my for in loop (
for(cc in mappedCostCentres)), why does:
cc.NameCC
evaluate to undefined, despite being able to see in firebug that the items in mappedCostCentres have the values that I expect?
- What better, or rather, working way is there to fill one array from another array?
Edit:
I am now trying the following code:
in my ViewModel (costCentreViewModel), I define:
this.GetCostCentres = function() {
$.ajax({url: "/Admin/Accounts/GetCostCentres/" + @id,
dataType: 'json',
data: {},
type: 'POST',
success: function (jsonResult) {
var mapped = $.map(jsonResult, function(item) {
return new costCentre(item.CostCentreId, item.IdTransactionType, item.Name, self)
});
self.costCentres = ko.observableArray(mapped);
alert(self.costCentres.length);
},
error: function (result) {
$('#ErrorDisplay').show().html('<p>' + result.responseText + '</p>');
}
});
};
Then I call (from outside the viewmodel definition):
var viewModel = new costCentreViewModel();
viewModel.GetCostCentres();
jQuery(document).ready(function () { ko.applyBindings(viewModel); });
It still doesn’t work. The problem in my mind is:
Why doesn’t this line work (everything else does):
self.costCentres = ko.observableArray(mapped);
I can only think it is the way I have defined my viewmodel object, using the constructor pattern, ie:
function myViewModel() {
this.costCentres= ko.observableArray([]);
...
this.GetCostCentres = function() {
$.ajax(....);
}
but I honestly haven’t a clue. JavaScript is defeating me right now. Maybe I should go back to Quantum Cosmology?
Looks like the main issue was that when trying to set the value of the observableArray on the result of an AJAX request, you were setting it equal to a new observableArray rather than setting the value of the existing one that was already bound to your UI.
So,
self.CostCentres= ko.observableArray(mapped)` will create a new observableArray that your UI is not currently bound against.self.CostCentres(mapped)would be the appropriate way to set an existing observableArray equal to an entirely new array.In a previous attempt, it did look like you were constructing a CostCentre twice to push it onto your observableArray. It is only necessary to create each CostCentre once.