Introduction
Tired of continuously clicking Next Page, Next Page, Next Page etc., to find the data you are looking for? I always thought this was just something you had to deal with when viewing data on the Internet, that was until I took a look at the new Microsoft live image search. Instead of forcing the user to click Next Page to paginate the data, the page catches the scroll event and fetches the next page of data and adds it to the output asynchronously. In this project, I will expand upon my previous project, Using LINQ to paginate your ObjectDataSource, replacing the standard grid with a scrolling paginated grid. This example works by first creating an Ext JS grid, making a call to a custom JSON RPC handler to retrieve data, and by adding an event listener to the scrolling event of our grid. This article will focus primarily on the AJAX portion of this project to read more on the underlying data pagination technique. Please take a look at the previous version of this project.
Topics covered in this project
- Singleton collections
- Querying objects using LINQ
- Using
Skip()
and Take()
with LINQ - Custom data pagination
- Creating a custom JSON RPC handler using Jayrock
- Using the Ext JS with ASP.NET and JSON RPC
- Working with JavaScript events and asynchronous JavaScript
Background
This project is an example of using LINQ to query a Singleton collection. This basically states that the collection persists in memory at the application level of your IIS process. This is achieved by using static or shared instances, or by using .NET web caching, or other third party cache mechanisms such as zcache or ncache. If you do not want to persist your data in memory, then rownumber is another way for you to achieve data pagination. In place of the class that the JSON RPC handler accesses to retrieve its data, you can replace the reference with a database query or object which loads directly from a database.
Using the code
The download Zip file contains the Visual Studio 2008 project and the source files necessary to run the online demo as well as a CSV containing a Zip code database and a Stored Procedure used to load the data referenced inside the code. You will need to update the connection string located in the web.config file to point to your data source after you have imported the Zip code data and ran the Stored Procedure script. In addition, this Zip contains the Ext JS library and the Jayrock DLLs required to run this project.
Points of interest
Before you get started, you will need a mechanism for retrieving the data asynchronously as your user scrolls through the list of data. I chose to use a generic handler which returns a JSON serialized string for this task to maximize performance and throughput.
C#
using System;
using System.Web;
using Jayrock.Json;
using Jayrock.JsonRpc;
using Jayrock.JsonRpc.Web;
using ZipCodeObjects;
using System.Collections.Generic;
public class GetZipCodes : JsonRpcHandler
{
[JsonRpcMethod("GetZipCodesCount")]
public int GetZipCodesCount()
{
return ZipCodeCollection.SelectCount();
}
[JsonRpcMethod("GetZipCodesList")]
public IEnumerable<ZipCode> GetZipCodesList(int ResultsPerPage, int PageNumber)
{
return ZipCodeCollection.GetZipCodes(ResultsPerPage, PageNumber);
}
}
VB.NET
Imports System
Imports System.Web
Imports Jayrock.Json
Imports Jayrock.JsonRpc
Imports Jayrock.JsonRpc.Web
Imports LinqListGridBind.ZipCodeObjects
Public Class GetZipCodes
Inherits JsonRpcHandler
<JsonRpcMethod("GetZipCodesCount")> _
Public Function GetZipCodesCount() As Integer
Return ZipCodeCollection.SelectCount()
End Function
<JsonRpcMethod("GetZipCodesList")> _
Public Function GetZipCodesList(ByVal ResultsPerPage As Integer, _
ByVal PageNumber As Integer) _
As IEnumerable(Of ZipCode)
Return ZipCodeCollection.GetZipCodes(ResultsPerPage, PageNumber)
End Function
End Class
This example uses no postbacks, viewstate, or session state. Data is stored in the DOM and accessed via JavaScript.
var store = null;
var grid = null;
var currentpos = 0;
var currentpage = 1;
var itemsperpage = 14;
var pagestofetch = 2;
var scrolloffset = 263;
var scrolloffsetinterval = 263;
Ext.onReady(function(){
var x = document.getElementById("currentcount");
x.innerHTML = (itemsperpage*pagestofetch);
var s = new GetZipCodes();
s.GetZipCodesCount(function(response) {
var x = document.getElementById("totalcount");
x.innerHTML = response.result;
});
var y = document.getElementById("skip1");
y.innerHTML = '0';
var z = document.getElementById("skip2");
z.innerHTML = '0';
var a = document.getElementById("take1");
a.innerHTML = (itemsperpage * pagestofetch);
var b = document.getElementById("take2");
b.innerHTML = (itemsperpage * pagestofetch);
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
var myData
s.GetZipCodesList(
itemsperpage*pagestofetch,
currentpage,
function(response) {
myData = response;
store = new Ext.data.JsonStore({data: myData,
root: 'result',
fields: ['zip',
'city',
'state',
'latitude',
'longitude',
'timeZone',
'dst']
});
grid = new Ext.grid.GridPanel({
store: store,
columns: [
{header: "Zip", width: 120, sortable: false, dataIndex: 'zip'},
{header: "City", width: 120, sortable: false, dataIndex: 'city'},
{header: "State", width: 120, sortable: false, dataIndex: 'state'},
{header: "Latitude", width: 120, sortable: false, dataIndex: 'latitude'},
{header: "Longitude", width: 120, sortable: false, dataIndex: 'longitude'},
{header: "Time Zone", width: 120, sortable: false, dataIndex: 'timeZone'},
{header: "DST", width: 120, sortable: false, dataIndex: 'dst'}
],
stripeRows: true,
height:350,
autoExpandColumn:6,
header: false,
title:'Zip Code Listing'
});
grid.render('zip-grid');
grid.addListener('bodyscroll',scrollListener);
});
});
function scrollListener(scrollLeft, scrollTop){
if ( scrollTop > currentpos )
{
if ( scrollTop > scrolloffset )
{
var state = grid.getView().getScrollState();
scrolloffset=scrollTop+scrolloffsetinterval;
currentpage=currentpage+1;
var myData
var s = new GetZipCodes();
s.GetZipCodesList(
itemsperpage*pagestofetch,
currentpage,
function(response) {
myData = response;
store.loadData(myData,true);
grid.getView().restoreScroll(state);
var x = document.getElementById("currentcount");
x.innerHTML = store.getCount();
var y = document.getElementById("skip1");
y.innerHTML = (((currentpage-1) * pagestofetch) * itemsperpage);
var z = document.getElementById("skip2");
z.innerHTML = (((currentpage-1) * pagestofetch) * itemsperpage);
var a = document.getElementById("take1");
a.innerHTML = (itemsperpage * pagestofetch);
var b = document.getElementById("take2");
b.innerHTML = (itemsperpage * pagestofetch);
});
}
currentpos=scrollTop;
}
}
Download the demo project for the complete source code and database. To view the LINQ and base class code online, please take a look at Using LINQ to paginate your ObjectDataSource.
History
- 4/03/2008: Posted code samples and article.
- 4/03/2008: Uploaded sample projects.