For some reason, foreach
in Knockout.js doesn't iterate through my observable array.
In my HTML I have this which works perfectly fine with the observable model:
<div class="field-group">
<label class="popup-label" for="email">Email</label>
<span class="email" data-bind="text: masterVM.employeeVM.Email"></span>
</div>
But in the same model, this code doesn't work:
<ul data-bind="foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.tagName"><i class="zmdi zmdi-delete"></i></span>
</li>
</ul>
There are two models:
Employee
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
self.errors = ko.validation.group(this, {
deep: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and Tag
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
self.errors = ko.validation.group(this, {
live: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and handler function:
var employeeHandler = function () {
var self = this;
self.getEmployeeDetails = function (header) {
$.ajax({
url: masterVM.controller.renderEmployeeDetails,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ id: header.data("employeeid") }),
success: function (result) {
masterVM.employeeVM = new observableEmployee(
result.model.Id,
result.model.Email,
result.model.Tags
);
ko.applyBindings(masterVM, $("#employee-planning-selected")[0]);
//header.parent().addClass('open');
//header.next().slideDown('normal');
//hideLoader(header);
console.log('get employee details');
$(document).on('click', "div.employee", onNameCardClick);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Error!');
}
});
}}
In my HTML file
<script>
masterVM = {
controller: {
renderEmployeeDetails: '@(Html.GetActionUrl<EmployeesController>(c => c.RenderEmployeeDetails(0)))'
},
employeeHandler: new employeeHandler(),
employeeVM: new observableEmployee(0, '', '', '', '')
}
ko.applyBindings(masterVM);
</script>
Tried something like this, and still nothing
<!--ko foreach: employeeVM.Tags -->
<span data-bind="text: $data.Tags"></span>
<!-- /ko -->
And no, there are no errors in the console, I have used KnockouJS context debugger which shows me that there are elements in this collection, even when I try to display them as an object it shows me a list of 4 elements.
Knockout version: 2.3.0
1). If you are binding masterVM
object in ko.applyBindings(masterVM)
, you don't need to specify that object again in your data-bindings.
So, it should be
foreach: { data: employeeVM.Tags, as: 'tag' }
And not
foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }
(I'm not sure how the first data-bind="text: masterVM.employeeVM.Email"
is working)
2). You don't need to call applyBindings
more than once. If you want to update the employee object, you can turn your employeeVM
into an observable and keep updating it inside getEmployeeDetails
method.
3) Your containerless control flow syntax won't work. (<!--ko foreach: employeeVM.Tags -->
). Inside this foreach, $data
is the current Tag
object in context. So, it should be <span data-bind="text: $data.TagName"></span>
Here's a minimal version of the code. Click on "Run code snippet" to test it. When you click on Update employee
button, I'm updating the employeeVM
observable and the data gets rendered again. Without calling applyBindings
again
var employeeHandler = function() {
var self = this;
self.getEmployeeDetails = function(header) {
var newEmployee = new observableEmployee(0, 'newEmployee@xyz.com', [{
Id: 3,
EmployeeId: 3,
TagId: 3,
tagName: 'Tag Name 3'
}]);
// You need to use employeeVM(newEmployee) instead of employeeVM = newEmployee
// Because employeeVM is an observable.
masterVM.employeeVM(newEmployee);
}
}
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
}
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
}
var masterVM = {
controller: {
renderEmployeeDetails: ''
},
employeeHandler: new employeeHandler(),
// change this to an observable
employeeVM: ko.observable(new observableEmployee(0, 'abc@xyz.com', [{
Id: 1,
EmployeeId: 1,
TagId: 1,
tagName: 'Tag name 1'
}]))
}
ko.applyBindings(masterVM);
document.getElementById("button").addEventListener("click", function(e) {
masterVM.employeeHandler.getEmployeeDetails()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="field-group">
<label class="popup-label" for="email">Email:</label>
<span class="email" data-bind="text: employeeVM().Email"></span>
</div>
<ul data-bind="foreach: { data: employeeVM().Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.employeeId"></span> <br>
<span class="popup-tag" data-bind="text: tag.tagId"></span><br>
<span class="popup-tag" data-bind="text: tag.TagName"></span>
</li>
</ul>
<button id="button">Update employee</button>