Reputation: 199
Let's say we have an observable array of N+1 elements. What i would like to achive is to build 3 by X grid with buttons from these elements. Like phone keypad or something like this.
What i have done so far is created a table with foreach and if in it:
<table>
<tbody data-bind="foreach: tableList">
<!-- ko if: isEof($index()) -->
<tr>
<!-- /ko -->
<td><button data-bind="text: name"></button></td>
<!-- ko if: isEof($index()) -->
</tr>
<!-- /ko -->
</tbody>
</table>
isEof() function should determine by list index wether we have already 3 elements rendered. If yes, then it will render tags. Also if index is 0, then it also renders elements. This is the code of the function:
function TableItem(data){
this.id=ko.observable(data.id);
this.name=ko.observable(data.name);
this.isEof = function(index){
if(index ==0){
return true;
}else{
if((index+3) % 3 === 0){
return true;
}else{
return false;
}
}
}
}
But i'm facing two problems with this setup.
1) With these if blocks enabled, button name binding does not work. When i remove ko if blocks, it will be rendered correctly.
2) ko if statements does not seem to work correctly. It will render out only those lines, where is also allowed to render.
I've made JSFiddle sample of my solution: http://jsfiddle.net/kpihus/3Lw7xjae/2/
Upvotes: 1
Views: 1320
Reputation: 6045
We can achieve this by simply passing $root
which will have tableList
and do the looping in simple way .
View Model:
function TableItem(data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.pickarray = ko.observableArray();
self.isEof = function (index, root) {
if (index === 0) {
self.pickarray(root.tableList().slice(index, index + 3));
return true;
} else if ((index + 3) % 3 === 0) {
self.pickarray(root.tableList().slice(index, index + 3));
return true;
} else {
return false;
}
}
}
var vm = function () {
this.tableList = ko.observableArray();
for (var i = 1; i < 15; i++) {
this.tableList.push(new TableItem({
id: i,
name: "name: " + i
}));
}
}
ko.applyBindings(new vm());
View :
<table data-bind="foreach:tableList">
<tr data-bind="if:isEof($index(),$root)">
<!-- ko foreach: pickarray -->
<td>
<button data-bind="text: id"></button>
</td>
<!-- /ko -->
</tr>
</table>
Trick here is very simple we just need to conditionally fill pickarray
which does the job for us .
Working Fiddle here
Upvotes: 1
Reputation: 10328
I would create a ko.computed
that converts your list of table items into an array of arrays:
var TableItem = function TableItem(data) {
this.id = ko.observable(data.id);
this.name = ko.observable(data.name);
};
var Vm = function Vm() {
this.tableItems = ko.observableArray([]);
this.columnCount = ko.observable(3);
this.columns = ko.computed(function() {
var columns = [],
itemCount = this.tableItems().length,
begin = 0;
// we use begin + 1 to compare to length, because slice
// uses zero-based index parameters
while (begin + 1 < itemCount) {
columns.push( this.tableItems.slice(begin, begin + this.columnCount()) );
begin += this.columnCount();
}
return columns;
// don't forget to set `this` inside the computed to our Vm
}, this);
};
vm = new Vm();
ko.applyBindings(vm);
for (var i = 1; i < 15; i++) {
vm.tableItems.push(new TableItem({
id: i,
name: "name: " + i
}));
}
This way, you can display your table by nesting two foreach
bindings:
<table>
<tbody data-bind="foreach: { data: columns, as: 'column' }">
<tr data-bind="foreach: column">
<td>
<button data-bind="text: name">A</button>
</td>
</tr>
</tbody>
</table>
If you haven't worked with ko.computed
before, they track whatever observables are accessed inside of them — in this case, this.tableItems
and this.columnCount
—, and whenever one of them changes, they run again and produce a new result.
this.columns
takes our array of table items
[TableItem, TableItem, TableItem, TableItem, TableItem, TableItem]
and groups them by this.columnCount
into
[[TableItem, TableItem, TableItem], [TableItem, TableItem, TableItem]]
Upvotes: 2