Kpihus
Kpihus

Reputation: 199

Create grid with knockoutjs

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

Answers (2)

super cool
super cool

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

janfoeh
janfoeh

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>

JSFiddle

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

Related Questions