I've been developing a web application that will display a table containing list of candidates. I want to display the candidate collection (get from the server) to the table, but with fixed row count to display, let's say 5 rows. So if the collection only have 2 candidates, the table will be having 5 rows, with the only first 2 rows contains the data, and the remaining 3 are empty rows.
I couldn't use the foreach binding to solve this, so I tried this post.
In that post the objects inside observableArray are not observable, so I tried to make them observable and it still works. But when I try in my code, it throws error in my js:
Unable to process binding "text: function (){return Candidates[0]().CandidateNumber }"
Message: AssignedCandidates[0] is not a function
I'm still not sure what I'm missing. Please help.
This is my js file:
var CandidateViewModel = function (data) {
var self = this;
self.CandidateId = ko.observable();
self.CandidateNumber = ko.observable();
self.Name = ko.observable();
self.Status = ko.observable()
ko.mapping.fromJS(data, {}, self);
}
var mappingCandidateList = {
Candidates: {
create: function (options) {
return new CandidateViewModel(options.data);
}
}
}
var CandidateListViewModel = function (data) {
var self = this;
self.Candidates = ko.observableArray();
self.AssignedCount = ko.observable();
self.ProcessingCount = ko.observable();
self.RejectingCount = ko.observable();
self.PassedCount = ko.observable();
self.FailedCount = ko.observable();
self.PagingInfo = ko.observable();
var getData = function (param) {
$.ajax({
url: api("Candidate/GetCandidates"),
data: param,
type: 'GET',
dataType: 'JSON'
}).done(function (data) {
ko.mapping.fromJS(data, mappingCandidateList, self);
});
}
}
ko.applyBindings(new CandidateListViewModel (), document.getElementById('candidate-container'));
and this is the html
<table>
<thead>
<tr>
<th>number</th>
<th>name</th>
<th>status</th>
</tr>
</thead>
<tbody>
<tr>
<td data-bind="text: Candidates()[0].CandidateNumber"></td>
<td data-bind="text: Candidates()[0].Name"></td>
<td data-bind="text: Candidates()[0].Status"></td>
</tr>
</tbody>
</table>
You can use the foreach
binding to get your guaranteed 5 rows, you'll just need a computed property that ensures the exact length of 5.
For example:
NR_OF_ROWS
)pureComputed
and bind to it using foreach
pureComputed
return an array of length NR_OF_ROWS
candidate
for every index that has one availablevar VM = function() {
const NR_OF_ROWS = 5;
this.candidates = ko.observableArray([
{ id: 1, firstName: "Jane", lastName: "Doe" },
{ id: 2, firstName: "John", lastName: "Doe" }
]);
this.displayRows = ko.pureComputed(function() {
const emptyCandidate = { id: "-", firstName: "-", lastName: "-", empty: true };
const candidates = this.candidates();
const rows = [];
for (let i = 0; i < NR_OF_ROWS; i += 1) {
rows.push(candidates[i] || emptyCandidate);
}
return rows;
}, this);
// For demo
this.newCandidate = {
firstName: ko.observable(""),
lastName: ko.observable(""),
add: function() {
this.candidates.push({
firstName: this.newCandidate.firstName(),
lastName: this.newCandidate.lastName(),
id: this.candidates().length + 1
})
}.bind(this)
}
}
ko.applyBindings(new VM());
tr:nth-child(even) { background: #efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody data-bind="foreach: displayRows">
<td data-bind="text: id"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tbody>
</table>
<div data-bind="with: newCandidate">
<input type="text" placeholder="first name" data-bind="textInput: firstName">
<input type="text" placeholder="last name" data-bind="textInput: lastName">
<button data-bind="click: add">add</button>
</div>
Edit: if you'd rather have a special view for an empty row, rather than a special viewmodel, you can use templates:
var VM = function() {
const NR_OF_ROWS = 5;
this.candidates = ko.observableArray([
{ id: 1, firstName: "Jane", lastName: "Doe" },
{ id: 2, firstName: "John", lastName: "Doe" }
]);
this.displayRows = ko.pureComputed(function() {
const candidates = this.candidates();
const rows = [];
for (let i = 0; i < NR_OF_ROWS; i += 1) {
rows.push(candidates[i] || emptyCandidate);
}
return rows;
}, this);
// For demo
this.newCandidate = {
firstName: ko.observable(""),
lastName: ko.observable(""),
add: function() {
this.candidates.push({
firstName: this.newCandidate.firstName(),
lastName: this.newCandidate.lastName(),
id: this.candidates().length + 1
})
}.bind(this)
}
}
ko.applyBindings(new VM());
tr:nth-child(even) { background: #efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody data-bind="foreach: displayRows">
<td data-bind="text: id"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tbody>
</table>
<div data-bind="with: newCandidate">
<input type="text" placeholder="first name" data-bind="textInput: firstName">
<input type="text" placeholder="last name" data-bind="textInput: lastName">
<button data-bind="click: add">add</button>
</div>