Using KnockoutJS to Bind to Unknown Dataset

I’m currently working on a web application that retrieves data from a web API and displays it in a web page, using KnockoutJS to provide the view model to bind to.  In some scenarios, the shape of the data that is retrieved will be unknown at design time.  The tables and columns that make up the dataset will be configurable via an admin section of the site.  So when it comes to displaying the data on the page, I won’t know how many tables, rows, or columns are available.

Here is how I retrieve the dataset from the API:

var dataSetsUri = baseApiUri + 'http://localhost:63978/api/datasets/';
vm.getDataSetData = function() {
    var uri = dataSetsUri + datasetId;
    vm.dataTables([]);
    ajaxHelper(uri, 'GET').done(function (data) {
        if (data != '') {
            for (var key in data) {
                var table = data[key];
                vm.dataTables.push(table);
            }
        }
    });
};

Here is a sample of the data returned from that web service call:

{
"Students":
	[
		{"Id":"1","FirstName":"Joe","LastName":"One","AddressLine1":"1234 Main St.","AddressLine2":"","City":"Jacksonville","State":"FL","Zip":"32256","EmailAddress":"joe@one.com","DateOfBirth":null}],
"Awards":
	[
		{"AwardId":"1","StudentId":"1","Amount":"1111","AwardDate":null,"AwardStatus":"1","DeclineReason":null,"AcademicYear":"2014"},
		{"AwardId":"4","StudentId":"1","Amount":"10101","AwardDate":null,"AwardStatus":"1","DeclineReason":null,"AcademicYear":"2014"}
		
	]
}

Using the ‘foreach’ binding, I want to display the data in my HTML page.  So I start with a div to act as a container.  Within the container, I want a table for each data table in my results:

<div class="container" data-bind="foreach: { data: dataTables, as: 'table' }">
   ...
</div>

Now that I have the tables, I want to have a column in the tables for each column in my results. Notice that the column names will be different for each table, so I need to discover them dynamically at run time. To accomplish this, I add a function to my view model which can be called from the HTML:

vm.getColumns = function (row) {
    var columns = [];
    for (var col in row) {
        columns.push(col);
    }
    return columns;
};

Now I can call that function from the Knockout binding.  I use the $root syntax to reference the view model scope.

<thead>
    <tr data-bind="foreach: { data: $root.getColumns(table[0]), as: 'columnName' }">
        <th data-bind="text: columnName"></th>
    </tr>
</thead>

In the table body, I will need a row for each row in the data, so again I use the Knockout foreach binding. I also reuse the getColumns function to create the table cells in each row:

<tbody data-bind="foreach: { data: table, as: 'row' }">
    <tr data-bind="foreach: { data: $root.getColumns(row), as: 'cell' }">
        <td>
            ...
        </td>
    </tr>
</tbody>

The final step is to display the value in each cell. I can use the ‘row’ and ‘cell’ aliases from the foreach bindings to easily access the values. The final result:

<div class="container" data-bind="foreach: { data: dataTables, as: 'table' }">
    <table class="table table-bordered">
        <thead>
            <tr data-bind="foreach: { data: $root.getColumns(table[0]), as: 'columnName' }">
                <th data-bind="text: columnName"></th>
            </tr>
        </thead>
        <tbody data-bind="foreach: { data: table, as: 'row' }">
            <tr data-bind="foreach: { data: $root.getColumns(row), as: 'cell' }">
                <td>
                    <span data-bind="text: row[cell]"></span>
                </td>
            </tr>
        </tbody>
    </table>
</div>

And the finished product:
knockout
Go forth and code!