Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Knockout Grid - Extended

4.93/5 (9 votes)
4 Mar 2015CPOL5 min read 40.9K   1.7K  
The article shows how to use the simplegrid which is used in knockout examples to which i've added support to accept column template to interact with outside DOM bindings, and stylish and simple pager and also multi select option.

Introduction

First lets talk about the knockoutgrid that we see in knockout examples. It is a simple grid which accepts columns, data to showcase your data in tabled manner. It is perfect for requirements where there are no interactions of grid with outside DOM. But there will be few requirements like for example, the grid shows list of students , the requirement is i need to view marks of individual student on click on some column of each student something as shown below: 

So by using custom template you can bind grid column template click event to interact with outside DOM elements.

Background

To understand grid functionality , i recommend you to practice the knockoutgrid from knockout examples

Knockout grid provides you with following configuration options. 

JavaScript
this.gridViewModel = new ko.simpleGrid.viewModel({
            data: this.items,
            columns: [
                { headerText: "Item Name", rowText: "name" },
                { headerText: "Sales Count", rowText: "sales" },
                { headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
            ],
            pageSize: 4
});

1) data - The collection of entity.

2) columns - The header text for you table and rowText is the property name in you collection.

3) pageSize - Specifies how many rows should each page contain.

Using the code

Coming to my code. I've modified knockout grid and added few more fields to accept column template , select all template and view model property to add all your bussiness logic of grid in it so that you can interact with outside DOM bindings to meet your bussiness requirements.

I've used the advantage of knockout template binding to fulfill the grid requirements. You can learn more about template binding here.

Here are the fields I've added to fulfill the grid requirements. 

1) columnTemplate - Its accepts an string ie, id of the script where you will have your markup for a row. 

2) ViewModel - Where you store all your bussiness logic to interact with grid columns like click event as  i've  shown  above to open a pop up modal to view specific student marks like such.

3) hasMultiSelect - Which is a boolean field if specified. you must add an <td> with checkbox in your column       template markup so that you can bind to your own ViewModel property to it. You must also specify select all template which is explain below.

4) headerMultiselectTemplate - Which is a string field accepts script id of template for header multiselect <th>    to bind it to your own ViewModel property so that you can customize or perform preprocess or post process works on select or deselect of rows.

5) addEnabled - Which is a boolean field which is used when you have a scenario like you want a add              template at the starting to add new entry in the grid. You need to specify add template to use this feature.

6) addTemplate - Which is a string field accepts script id of template to specify customized template for add    row, you can have any html like drop downs, text inputs and bind properties to your own View Model properties.

The grid will fallback to knockout simple grid style of simple text binding if you won't any of the above fields and it will display simple grid with my customized paging as shown below:

Code to use simple grid:

JavaScript
this.simpleStudentGrid = new ko.simpleGrid.viewModel({
            data: this.studentModel.students,
            pageSize: 10,
            columns: [{ headerText: "Roll No", rowText: "srNo" }, { headerText: "Name", rowText: "name" }],
        });

and bind property to your DOM.

Example 1: (Using column template to view marks of a student)

To use column property you need to have columnTemplate, ViewModel to have bussiness logic to handle click event to view marks that will pop up a bootstrap modal window. 

Grid binding: 

JavaScript
this.gridWithColumnTemplate = new ko.simpleGrid.viewModel({
            data: this.studentModel.students,
            pageSize: 10,
            columnTemplate: "gridWithColumnTemplate",
            columns: [{ headerText: "Roll No", rowText: "" }, { headerText: "Name", rowText: "" }, { headerText: "View Marks", rowText: "" }],
            ViewModel: studentModel
        });

ViewModel : (One of the property of view model studentModel as specified in above configuration)

JavaScript
self.ViewMarks = function (data, e) {
            e.stopPropagation();
            self.name(data.name);
            self.srNo(data.srNo);
            self.marks(data.marks);
            showTopModal("studentModal");
};

Template :

JavaScript
<script id="gridWithColumnTemplate" type="text/html">
    <td data-bind="text: srNo"></td>
    <td data-bind="text: name"></td>
    <td><a href="#" data-bind="click: $parent.ViewModel.ViewMarks">View marks</a></td>
</script>

output: 

When you click on View marks above a bootstrap pop up modal opens with marks of specific student.

You can have any html input elements in your template , like have a remove glyphicon which will remove specific user on click on it , have remove business logic in your view model.

Example 2: (Using column template to remove users )

Grid binding won't change from above except the columnTemplate changes.

View Model : ( Remove user property )

JavaScript
self.RemoveStudent = function (data, e) {
            e.stopPropagation();
            alert("All grids are bind to same data source, Are you sure you want to delete data.");
            self.students.remove(data);
        };

Template:

JavaScript
<script id="gridRemoveStudent" type="text/html">
    <td data-bind="text: srNo"></td>
    <td data-bind="text: name"></td>
    <td><a href="#" data-bind="click: $parent.ViewModel.RemoveStudent"><i class="glyphicon windows-icon-trash"></i></a></td>
</script>

Output:

Example 3: (Using addTemplate to have your custom style add template like drop downs , input boxes)

Requirements such as add new entry when click a button which is not inside grid , but outside the grid DOM.To enable such addTemplate you need to use two fields addEnabledaddTemplate 

Grid binding:

JavaScript
this.gridWithAddTemplate = new ko.simpleGrid.viewModel({
            data: this.studentModel.students,
            columns: [{ headerText: "Roll No", rowText: "" }, { headerText: "Name", rowText: "" }, { headerText: "View Marks", rowText: "" }, { headerText: "Actions", rowText: "" }],
            pageSize: 10,
            columnTemplate: "gridAddStudentStudent",
            addEnabled: studentModel.addEnabled,
            addTemplate: "addStudentTemplate",
            ViewModel: studentModel
        });

View Model:

JavaScript
self.addEnable=ko.observable(false);
self.EnableAdd = function () {
            self.addEnabled(true);
        };
self.cancelEdit = function () {
            self.addEnabled(false);
        };
self.AddStudent = function () {
            self.student.marks = { maths: 90, physics: 82, chemistry: 80 };
            self.student.srNo = self.srCounter();
            self.students.push(self.student);
            self.student = new student();
            self.addEnabled(false);
            self.srCounter(self.srCounter() + 1);
        };

Template:  ( 2 Template one for  add row and one for complete grid(which will be same as above))

JavaScript
<script id="gridAddStudentStudent" type="text/html">
    <td data-bind="text: srNo"></td>
    <td data-bind="text: name"></td>
    <td style="width:166px;"><a href="#" data-bind="click: $parent.ViewModel.ViewMarks">View marks</a></td>
    <td style="width:150px;"></td>
</script>

<script id="addStudentTemplate" type="text/html">
    <!-- ko with: ViewModel.student -->
    <td><input type="text" class="form-control input-sm" data-bind="value: $parent.ViewModel.srCounter" disabled="disabled"/></td>
    <td><input type="text" class="form-control input-sm" data-bind="value: name"/></td>
    <td>View marks</td>
    <td style="width:150px;text-align: center;">
        <a href="#" data-bind="click: $parent.ViewModel.AddStudent" style="margin-right:10px;" title="Save"><i class="glyphicon windows-icon-save"></i></a> <span style="border-left:2px solid #F4F4F4;"></span> <a href="#" data-bind="click: $parent.ViewModel.cancelEdit" style="margin-left:10px;" title="Cancel"><i class="glyphicon windows-icon-cancel"></i></a>
    </td>
    <!-- /ko -->
</script>

Output:

when you click on add student, it will add a new entry on top of grid as below:

And when you type in text and click save it will add it to the grid as follows:

Example 4: (Using hasMultiSelect feature and bind to your view model properties and process pre and post operations before selecting and after selecting rows)

Grid binding:

To your multi select template you need to have two fields specified in the grid hasMultiSelectheaderMultiselectTemplate and in your grid template add first row as checkbox to bind each column to your checked property.

JavaScript
this.gridWithSelectAll = new ko.simpleGrid.viewModel({
            data: this.studentModel.students,
            columns: [{ headerText: "Roll No", rowText: "" }, { headerText: "Name", rowText: "" }, { headerText: "View Marks", rowText: "" }],
            pageSize: 10,
            columnTemplate: "gridWithSelectAll",
            hasMultiSelect: true,
            headerMultiselectTemplate: "mainGridMultiSelectTemplate",
            ViewModel: studentModel
        });

View Model:

JavaScript
self.selectedStudents = ko.observableArray();
self.selectedAllStudents = ko.pureComputed({
            read: function () {
                return self.selectedStudents().length === self.students().length;
            },
            write: function (value) {
                self.selectedStudents(value ? self.students().slice(0) : []);
            }
        });

Template:

To use multiselect template specify header select all template so that you can bind it to your own property to you further. And in main grid template add a <td><input type="checkbox"/></td> row as first row and bind to your own view model property to process further.

JavaScript
<script id="gridWithSelectAll" type="text/html">
<script id="gridWithSelectAll" type="text/html">
    <th style="text-align: center; width: 50px;"><input type="checkbox" data-bind="attr:{id:srNo+'_'+name},checkedValue: $data, checked: $parent.ViewModel.selectedStudents" /><label data-bind="attr:{for:srNo+'_'+name}"></label></th>
    <td data-bind="text: srNo"></td>
    <td data-bind="text: name"></td>
    <td><a href="#" data-bind="click: $parent.ViewModel.ViewMarks">View marks</a></td>
</script>

<script id="mainGridMultiSelectTemplate" type="text/html">
    <th style="text-align: center; width: 50px;"><input type="checkbox" id="srSelectAll" data-bind="checked: ViewModel.selectedAllStudents" /><label for="srSelectAll"></label></th>
</script>

Output:

When you select rows, as i binded it to show selected student names it will show as follow:

When you click on select all rows, it will show as follow:

You can see the paging functionality by adding more records and play back and forth with the pager. The site is styled with bootstrap. In code you will find knockoutgrid.css which has grid styling and site.css has site layout styles , custom checkbox styles to have same checkbox feel across browsers.

Paging sample screens:

 

Thanks for reading in advance :-P 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)