Ajax Paging Through Records On Scroll

Posted By : todd sharp Posted At : August 21, 2007 3:32 PM Posted In: Ajax, JavaScript, ColdFusion

5

I've always thought one of the cooler features on dzone was how they paged through the entries as you scroll down the list. This seems a ton more user friendly then the traditional 'next/previous' links.

I decided to see how easy it would be to recreate this functionality using ColdFusion 8 and as usual the answer was 'pretty easy'. There is a bit of JavaScript, but it's nothing too difficult to wrap your head around.

Check the demo here first. I intentionally sleep the thread for 1 second so you can see the loading icon - so don't think that it's just slow :).

On to the code bits. The first thing I have going here in this example is a super simple cfajaxproxy:

<cfajaxproxy cfc="test" jsclassname="test" />

This lets me call the function to get the display data with JavaScript - which is important in this example rather then using binding or ColdFusion.navigate(). See, I'd rather that the user really not know what was going on - and using those other methods would swap out the contents of the container and give a loading icon while we made the Async call. Normally that's ok, I just wanted this to be seemless because later we'll append the page of data to our innerHTML rather then fully replacing it.

Next I create the JS variable representing the Ajax proxy and declare a few other variables that will be used later on:

var testProxy = new test();
var startRow = 1;
var loading = "";

The main JS functions that handle the data retrieval are here:

getData = function(){
    if(!loading){
        loading = true;
        testProxy.setCallbackHandler(populateData);
        var loadIcon = document.getElementById('loadingIcon');
        loadIcon.style.visibility = 'visible';
        var data = testProxy.getRecords(startRow);    
    }    
}

So when this function is called (we'll look at that later) it checks the global 'loading' variable to make sure it's not already getting some data. Next I set a callback handler for the proxy to the 'populateData' function. This ensures that I'll be able to hide the loading gif when the data retrieval is finished. Here's the callback function:

populateData = function(d){
    var container = document.getElementById('display');
    var loadIcon = document.getElementById('loadingIcon');
    container.innerHTML = container.innerHTML + d;
    loadIcon.style.visibility = 'hidden';
    loading = false;
}

The callback takes one argument, the result of the CFC invocation. This function takes that data and appends it so the innerHTML of the display container. Finally I reset the loading flag to false signifying that the request is complete. Here's the display container:

<div id="display" style="height: 100px; width: 300px; overflow-y: scroll;" onscroll="javascript:scrollAction();"></div>
<div id="loadingIcon" style="visibility: hidden;"><img src='/CFIDE/scripts/ajax/resources/cf/images/loading.gif'/></div>

So just a fixed height div with an that fires another JS function onscroll. That function looks like this:

scrollAction = function(){
    var container = document.getElementById('display');
    if(!loading){
        if(container.scrollTop+container.clientHeight == container.scrollHeight){
            startRow = parseInt(startRow) + 10;
            getData();
        }
    }    
}

Again I check that there is not a current request running by checking the 'loading' variable. The next line was a bit tricky:

if(container.scrollTop+container.clientHeight == container.scrollHeight)

So this checks the scrollTop variable against the scrollHeight to determine if the current scroll is at the bottom of the div (ready to get more records in other words). However since the div's height will grow everytime we append more data I had to ensure that the variable div height was accounted for. Adding the clientHeight to the scrollTop handled that nicely.

If the conditional passes then we increment the global startRow variable and call the getData() function.

Oh also - A quick load event in the body makes sure we intially load the first 'page' of data.

<body onload="getData(1);">

Full code for this demo is attached.

Comments (5)

Sam Farmer's Gravatar Very cool.

Bhaarat's Gravatar This is very cool indeed. I've been looking for help in creating this functionality for dayS!

Although I was looking for a jquery equivalent of this but this will do :)

thanks again

Olaf's Gravatar Hi

Thanks for the great post. but can you or someone else out there please show how to do this with PHP. i don't know any cold fusion and even though this is a great feature i am unable to use it :(

Viraj Doshi's Gravatar This is very nice. i liked it.
I need to create the same in asp.net. i m using the UI suite called "componentArt" which has its own grid and callback trigger.
I m specially interested to know what happens on the server and how does the data get appended to exitsting records. Normally, asp.net grids render the whole tables with <table>...</table> instead of rows.
I appriciated your help in advance. Thanks

Ryan Balchand's Gravatar I made some change to this to pull from a database. Delete the Application.cfc file, you don't need it in this case.

make the following changes to the test.cfc to pull from a query.

<cfcomponent>

   <cfset variables.maxRows = 10 />
   <cffunction name="getRecords" access="remote" output="false" returntype="string">
      <cfargument name="startRow" required="true" />
      
      <cfset var returnStr = "" />
      
      <cfquery name="getdata" datasource="#application.datasource#">
         SELECT *
         FROM table
      </cfquery>
      
      <cfsavecontent variable="returnStr">
         <cfoutput query="getdata" startrow="#arguments.startRow#" maxrows="#variables.maxRows#">
         <li>ID: #variables.getdata.id[currentRow]# | Name: #variables.getdata.name[currentRow]#</li>
         </cfoutput>
      </cfsavecontent>
      <!--- simulate a long request --->
      <cfset sleep(1000) />
      <cfreturn returnStr />
   </cffunction>
</cfcomponent>