basic javascript and knockout.js in MVC 3 app: problem with for in loop over an array of myCustomObject items
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.
The problem has largely been due to:
my lack of knowledge of javascript.
copying the knockoutjs.com tutorial for loading and saving data using knocking and not realising that the tutorial framework was missing any lines of js code to call the viewmodel object.
So when it didn't work, I set off on a number of red herrings that just drove me deeper and deeper into the mud.
Niemeyer's comments below the question helped me to get back on track. But for anyone with similar levels of javascript ignorance as me, you can copy or adapt the tutorial code on knockoutjs.com, just remember to add, at the bottom the equivalent of (ie, depending on what your code does):
var viewModel = new costCentreViewModel();
viewModel.GetCostCentres();
jQuery(document).ready(function () { ko.applyBindings(viewModel); });
This effectively runs your code, as opposed to odd things happening, that I am not equipped to describe or analyse...
Note
I have added this answer in case anyone comes looking with the same problem as me, and to hightlight what did work, which was following RP Niemeyer's suggestions and getting the thing to work.
If RP Niemeyer adds an answer, summarising his suggestions, I will delete this answer and mark his as the solution.
精彩评论