Click here to Skip to main content
13,350,029 members (48,757 online)
Click here to Skip to main content
Add your own
alternative version

Stats

6.6K views
128 downloads
9 bookmarked
Posted 26 Jul 2017

Client side Grid display, Editing, Paging, Resizing, Filtering and Sorting using Knockout and JQuery

, 28 Jul 2017
Rate this:
Please Sign up or sign in to vote.
A client side grid display, editing, paging, resizing, filtering and sorting using Knockout and JQuery

Introduction

As paradigm shifted from web form (code behind) to client side script, most of the applications are developing on client side framework built on JavaScript. Knock out and JQuery is one of the popular client side scripts. In every application (mostly business application), we are required to show data in tabular structure along with different filter, paging, sorting, editing, resizing pertaining to table. To considering these mentioned features, I will try to cover all points in this article using Knockouts framework with the help of JQuery.

After completion of this article, the reader should be able to understand Knockout, JQuery, event, function, selector etc.

The Knockout and JQuery scripts can be downloaded from the below URLs:

Following is the core functionality which we will discuss:

  • Binding (Display)
  • Editing
  • Paging
  • Resizing
  • Filtering
  • Sorting

Binding (Display)

In knockout, we render table/Grid by applying binding (result collection) to the table’s body and loop through iteration. In each iteration, HTML content (tags) is rendered along with dynamic data bind with in </TD> (HTML tag).

First, we need to create observable to hold and bind data.

var self = this;//
self.items = ko.observableArray();
self.itemsTemp = ko.observableArray();

Now, we need to get data from the server. To achieve this, we will make an Ajax call. On success request, the result will be stored in both self.items and self.items.Temp. There is a reason to store same data in two observables which will be discussed later (Filtering stage) in this article.

self.loadData = function (from, count) {

            $.ajax({
                type: "POST",
                url: KoGrid.aspx/GetEmployeeList',
                contentType: "application/json;",
                data: "{'from':" + from + ",'count':" + count + "}",
                dataType: "json",
                success: function (results) {
                    self.items(results.d.employeeuserList);
                    self.itemsTemp(results.d.employeeuserList);
                  },
                error: function (err) { alert(err.status + " - " + err.statusText); }
            });
        }

The code behind web method will get data from the database. In order to avoid complexity and focus on the grid, the dummy data is generated from the web method.

[WebMethod]

public static RetunObject GetEmployeeList(int from, int count){

 List<employeeuser> empList = new List<employeeuser>(){
     new employeeuser {userId=1, name = "A", price = 5.5, sales = 2 },
     new employeeuser {userId=2, name = "AB", price = 6.5, sales = 32 },
     new employeeuser {userId=3, name = "Al", price = 7.5, sales = 42 },
     new employeeuser {userId=4, name = "FAD", price = 8.5, sales = 52 },
     new employeeuser {userId=5, name = "Az", price = 9.5, sales = 62 },
     new employeeuser {userId=6, name = "VAF", price = 1.1, sales = 72 },
     new employeeuser {userId=7, name = "AG", price = 2.5, sales = 83 },
     new employeeuser {userId=8, name = "VAH", price = 3.2, sales = 92 },
     new employeeuser {userId=9, name = "AI", price = 4.5, sales = 102 },
     new employeeuser {userId=10, name = "AJ", price = 44.5, sales = 112 },
     new employeeuser {userId=11, name = "A", price = 5.5, sales = 2 },
     new employeeuser {userId=12, name = "VAB", price = 6.3, sales = 32 },
     new employeeuser {userId=13, name = "Al", price = 7.5, sales = 45 },
     new employeeuser {userId=14, name = "AD", price = 8.5, sales = 52 },
     new employeeuser {userId=15, name = "RAz", price = 9.4, sales = 62 },
     new employeeuser {userId=16, name = "AF", price = 1.5, sales = 78 },
     new employeeuser {userId=17, name = "AG", price = 2.5, sales = 82 },
     new employeeuser {userId=18, name = "FVAH", price = 3.5, sales = 92 },
     new employeeuser {userId=19, name = "AI", price = 4.5, sales = 102 },
     new employeeuser {userId=20, name = "AJ", price = 44.6, sales = 102 },
     new employeeuser {userId=21, name = "FA", price = 5.5, sales = 2 },
     new employeeuser {userId=22, name = "AB", price = 6.5, sales = 32 },
     new employeeuser {userId=23, name = "RAl", price = 7.5, sales = 12 },
     new employeeuser {userId=24, name = "AD", price = 8.7, sales = 59 },
     new employeeuser {userId=25, name = "Az", price = 9.5, sales = 62 },
     new employeeuser {userId=26, name = "AF", price = 1.5, sales = 73 },
     new employeeuser {userId=27, name = "VAG", price = 2.5, sales = 82 },
     new employeeuser {userId=28, name = "AH", price = 3.5, sales = 92 },
     new employeeuser {userId=29, name = "AI", price = 4.5, sales = 107 },
    new employeeuser {userId=30, name = "AJ", price = 44.8, sales = 112 }
   };

  return new RetunObject() { employeeuserList = empList.Skip(from).Take(count).ToList(),
             TotalCount = empList.Count() };
  }

 public class employeeuser {
  public int userId { get; set; }
  public string name { get; set; }
  public int sales { get; set; }
  public double price { get; set; }
 }

public class RetunObject{
 public int TotalCount { get; set; }
 public List<employeeuser> employeeuserList { get; set; }
}

After successfully retrieving data from Server into observable array, the following table will be rendered dynamically by iterating elements in itemsTemp observable.

<table id="tblHeading" border="1" style="width: 100%;">
  <thead>
    <tr>
       <td>
          <a class="cursor bold " >UserId</a>
       </td>
       <td>
          <a class="cursor bold" >Name</a>
       </td>
       <td>
          <a class="cursor bold" >Price</a>
       </td>
       <td>
          <a class="cursor bold" >Sales</a>
       </td>
       <td>
       </td>
    </tr>
  </thead>
  <tbody data-bind="foreach: itemsTemp">
    <tr>
       <td id="userId" data-bind="text: userId"></td>
       <td data-bind="text: name"></td>
       <td data-bind="text: price"></td>
       <td data-bind="text: sales"></td>
       <td>
           <button value: userId">Edit</button>
           <button value: userId">Delete</button>
       </td>
    </tr>
  </tbody>
</table>

Call the function on page load to load and render the grid.

self.formLoad = function () {
            self.loadData(0, 5);
        }
self.formLoad();

The following table will be rendered as output after applying binding model view with view.

$(document).ready(function () {
        ko.applyBindings(new PagedGridModel());
    });

Here is the output:

Editing

After successful grid creation, now let’s discuss edit functionality of grid. Simply, we need to bind click event with relevant functions at Edit and Delete button.

<button data-bind="click: $root.EditClick, value: userId">Edit</button>
<button data-bind="click: $root.DeleteClick, value: userId">Delete</button>

The following functions will be called on edit and delete click respectively. Here, we are only showing alert with row id. You can use it as per your requirement.

self.EditClick = function (a) {
            alert("UserId " + a.userId + " edit");
        }
        self.DeleteClick = function (a) {
            alert("UserId " + a.userId + " delete");
        }

Here is the output on click on Edit or Delete button.

Paging

The grid’s paging is one of the features which is used when there is a huge amount of records to display. We can perform paging in the grid by adding few lines of code.

First, add paging observable array in view model.

self.itemsPaging = ko.observableArray();
self.pageSize = ko.observable();

The self.pageSize is the size of grid, which will be filled on load as default value or when user will change grid size value. This will be discussed more on Resizing stage.

Now, create paging function which will get paging range values in self.itemsPaging array.

self.paging = function (totalRecord) {
          self.itemsPaging([]);
          var filtercount = self.pageSize();
          var totalIteration = Math.ceil(totalRecord / filtercount);
          if (totalIteration > 1) {
              for (var i = 1; i <= totalIteration; i++) {
                  self.itemsPaging.push(i);
              }
          }
      }

Now the following HTML will loop through iteration. In each iteration, a span with page number will be bind as display value with click event(“moveToPage”).

<div data-bind="foreach: itemsPaging" style="font-size: xx-large; width: 100%;">
             &nbsp;<span style="background-color: lightgrey">
                 <a href="#" style="margin: 15px;" data-bind="text: $data,
                    click: $root.moveToPage "></a>
             </span>&nbsp;
</div>

Here is the output after paging implementation.

One more thing is remaining in complete functionality of paging. On click on page number, get data accordingly from server or locally. In order to achieve this, we have bind “moveToPage” function with each number. In moveToPage, we are getting the number of page to visit and calculate the number by Page size and page number. After this, we are simply calling “loadData” function which will bring data from server. The same thing can be achieved by getting all data from server in one go and hold it in temporary observable array.

self.moveToPage = function (PageNumber) {
          var count = self.pageSize();
          var from = (PageNumber - 1) * count;
          self.loadData(from, count);
      }

Resizing

Grid resizing provides the functionality to restrict the number of records on a page. The paging functionality is directly associated with resizing because as we change the size (number of records) of page, the paging numbers below the grid will also update. To implement resizing functionality is also a matter of writing some lines of code.

Add dropdown (Select) on the form with some options for sizing.

<b>Page Size: </b>
  <select id="Scount" data-bind="value: pageSize, event: { change: pageSizeChange }"
          class="ddlWidth260 ddl">
     <option value="5">5</option>
     <option value="10">10</option>
     <option value="20">20</option>
     <option value="50">50</option>
   </select>

Now we need to bind the select (drop down) value with observable in order to get current size value.

Here, we will use “pageSize “observable which we have mentioned before. After this, bind select with “PageSizeChange” function on change event. In pageSizeChange function, we are simply calling the records from zero (0) to size of page and paging will also be reset accordingly.

self.pageSizeChange = function () {
          self.loadData(0, self.pageSize());
      }

Filtering

Another very important functionality pertaining to grid is filtering records. Again, we can make it possible to filter the records by rendering textbox (input) on grid header and bind with relevant event which is “filtering”.

First we need to add input, assign a class of “filter” along with “Keyup” event and column name property so that it can identify which column needs to be filtered, on all those </TD> (HTML tag) where we want to show filter functionality. We can do this by adding the following HTML in table.

<thead>
 <tr>
  <td>
    <a class="cursor bold " data-bind="click: 
    $root.sortGrid.bind(this, 'userId')">UserId</a><br />
    <input type="text" class="width100 filter" columnname="userId" 

           data-bind="event: { keyup: $root. filtering.bind(this, 'userId') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'name')">Name</a><br />
    <input type="text" class="width100 filter" columnname="name"

           data-bind="event: { keyup: $root. filtering.bind(this, 'name') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'price')">Price</a><br />
    <input type="text" class="width100 filter" columnname="price"

           data-bind="event: { keyup: $root. filtering.bind(this, 'price') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'sales')">Sales</a><br />
   <input type="text" class="width100 filter" columnname="sales" 

          data-bind="event: { keyup: $root. filtering.bind(this, 'sales') }" />
  </td>
  <td>
  </td>
 </tr>
</thead>

Then create a function with the name of “filtering” where we will filter the data based on user input. In this function, first we will get all inputs having class “filter”. As we have discussed earlier, the same data (result collection) will be stored in two observables. The reason for doing this is to hold data for user filtering. If we will store data in one observable array and user filters some data, it will filter the result but what if user removes all filters, then we do not have complete data as we have filtered it. That’s the reason to store data in two observable so that if user will apply filtration on grid and then reset grid, the data from second observable (self.items()) will be stored to self.itemsTemp. By doing this, we will avoid unnecessary calling to the server.

Now all fetched input will iterate for applying relevant filtration over grid and last the resultant data will be bind again to the table.

self.itemsTemp(self.items());

Here is the output result before filtering.

Output result after applying filtration.

Output result after reset/remove filtration.

Sorting

Another important functionality of grid is sorting. In knock Out or any other client framework, we can achieve sorting very easily by binding event with header anchor text and pass column name in it so that we can identify the column to which we will sort the data.

<thead>
 <tr>
  <td>
    <a class="cursor bold " data-bind="click: 
    $root.sortGrid.bind(this, 'userId')">UserId</a><br />
    <input type="text" class="width100 filter" columnname="userId" 

           data-bind="event: { keyup: $root.filtering.bind(this, 'userId') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'name')">Name</a><br />
    <input type="text" class="width100 filter" columnname="name" 

           data-bind="event: { keyup: $root.filtering.bind(this, 'name') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'price')">Price</a><br />
    <input type="text" class="width100 filter" columnname="price"

           data-bind="event: { keyup: $root.filtering.bind(this, 'price') }" />
  </td>
  <td>
    <a class="cursor bold" data-bind="click: 
    $root.sortGrid.bind(this, 'sales')">Sales</a><br />
   <input type="text" class="width100 filter" columnname="sales"

          data-bind="event: { keyup: $root.filtering.bind(this, 'sales') }" />
  </td>
  <td>
  </td>
 </tr>
</thead>

And finally, create binding event which will sort records based on that column.

self.sortGrid = function (data, obj) {
                self.itemsTemp.sort(function (a, b) {
                var x = a['' + data];
                var y = b['' + data];
                if (x < y) { return -1; }
                if (x > y) { return 1; }
                return 0;
                });
                }

Here is the grid sorted on price Column.

License

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

Share

About the Author

israrali
Pakistan Pakistan
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionExcellent work Pin
Member 123449681-Aug-17 1:48
memberMember 123449681-Aug-17 1:48 
PraiseVery good article Pin
Member 1323174528-Jul-17 10:51
memberMember 1323174528-Jul-17 10:51 
QuestionDownloads Missing Pin
Member 1188808927-Jul-17 10:02
memberMember 1188808927-Jul-17 10:02 
AnswerRe: Downloads Missing Pin
israrali31-Jul-17 10:59
memberisrarali31-Jul-17 10:59 
Bugall images are missing Pin
Mou_kol26-Jul-17 23:41
memberMou_kol26-Jul-17 23:41 
QuestionGrids with Knockout Pin
Sharp Ninja26-Jul-17 7:44
memberSharp Ninja26-Jul-17 7:44 
PraiseKnockout Pin
Member 1333007526-Jul-17 5:34
memberMember 1333007526-Jul-17 5:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.180111.1 | Last Updated 28 Jul 2017
Article Copyright 2017 by israrali
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid