AJAX Pagination with Coldfusion and Jquery

In my continued fascination with Pagination (does that make me a Geek?) i am detailing here how to achieve it with AJAX (Jquery/JSON really) and Coldfusion. You can see a running example of the code here, and download all the code here. If you are interested in learning Jquery i recommend reading the book Learning Jquery.

Just to diverge slighly if you were like i was and really wanted to get to grips with AJAX. But got confused as to where to start due to the amount of different frameworks around, not to mention Coldfusion's own AJAX tools, like CFAjaxproxy. Then let me enlighten you. CFAjaxproxy is like a mini JS framework. So if you are wanting to use Jquery then i think you don't really need to worry about what CFajaxproxy does. I think Cfajaxproxy suits best if you need to get something done in a hurry and can't spare the time to learn Jquery.

Features

  • Displays record set query data one page at a time.
  • Page numbered links are also built and placed displayed records
  • Allows for sorting of returned results by clicking on table headers.
  • Includes highlighting of current page link and showing only up to 20 page numbers/links on page at any one time.
  • CF returns only the records needed for each page (ie 10 at a time) rather than the whole record set.
  • Has previous and next links at either end of page links.

The CFC
Each time the CFC is called from Jquery it returns a struct containing a result set of 10 records along with info about the archive being queried. Which can be used to help show user how many records they are paging through. Accepts 2 parameters: PageNumber - needed to help the CFC determine what records to return. orderBy - passes in what database column to order the returned query by.

<cfcomponent displayname="paginationRealData" hint="calculates and returns pagination info and record set" output="false">


   <cffunction name="pageInfo" displayname="pageInfo" access="remote" output="false" returnFormat="json">
      <cfargument name="pageNumber" type="Numeric" required="true">
      <cfargument name="orderBy" type="string" required="true">
      
<cfset var result = structNew()>

<CFIF not isDefined("arguments.pageNumber") >
<cfset arguments.pageNumber = 1>
</CFIF>

<cfquery datasource="which_new" name="myQuery">
select name, region, area, population from bbc
order by #arguments.orderBy#
<cfif #arguments.orderBy# is "population" or #arguments.orderBy# is "area">DESC</cfif>
</cfquery>


<cfset result.orderBy = #arguments.orderby#>
<!---determines how records are shown on one page --->
<cfset result.numberOfRecordsOnPage = 10>
<!---calculate the number of pages the archive will have --->
<cfset numberOfPagesNoCeil = (#myQuery.recordCount# / #result.numberOfRecordsOnPage#)>
<cfset result.numberOfPages = #ceiling(numberOfPagesNoCeil)#>
      
      <!---here using url.pagenumber to work out what records to display on current page --->
      <cfset result.from = ((#arguments.pageNumber# * #result.numberOfRecordsOnPage#) - #result.numberOfRecordsOnPage#) + 1>
    <cfset result.to = (#arguments.pageNumber# * #result.numberOfRecordsOnPage#)>
      
      <!---if on last page display the actual number of records in record set as the last to' figure'. Otherwise it gives
      a false reading and gives the pagenumber * numberOfRecordsOnPage which is always a multiple of 10--->

      <cfif #result.to# eq (#result.numberOfPages# * 10)>
       <cfset result.to = myQuery.recordCount>
      </cfif>
      
<!---Query to send back the 10 records relevant to pageNumber --->
<!--- Create a new 4-column query, specifying the column data types --->
<cfset recordsToGoBack = QueryNew("field1, field2, field3, field4", "VarChar, VarChar, VarChar, VarChar")>
<!--- --->
<cfloop query="myQuery" startrow="#result.from#" endrow="#result.TO#">
<!--- Set the values of the cells in the query --->
<cfset temp = QueryAddRow(recordsToGoBack)>
<cfset temp = QuerySetCell(recordsToGoBack, "field1", #name#)>
<cfset temp = QuerySetCell(recordsToGoBack, "field2", #region#)>
<cfset temp = QuerySetCell(recordsToGoBack, "field3", #area#)>
<cfset temp = QuerySetCell(recordsToGoBack, "field4", #population#)>
</cfloop>

<cfset result.recordsToGoBack = #recordsToGoBack#>
      
      
      <cfreturn result/>
   </cffunction>
</cfcomponent>

The JS (Jquery code) I am unable to display the Jquery code on my blog, as it come up all skew-whiff when i cut and paste via blog CFC admin. You can follow and view the JS code here. The code is mostly Jquery.

Displaying the records / Data

To communicate with the CFC i used Jquery's global function getJSON(). Then it was a matter of traversing the returned JSON object and using the values in there to help build a string containing a HTML table that displays the returned records. The HTML string need not be a table. It can be as simple as a
tag appended to the returned database values. Once i retrived and built my strings i then appended the strings to the appropriate divs on my .cfm page.

I came across a BIG gotcha when traversing the JSON. "THE JSON VARIABLES RETURNED FROM A CFC ALL SEEM TO BE IN UPPER CASE. AS JAVA SCRIPT IS CASE SENSITIVE REMEMBER TO REF THOSE VARIABLES IN UPPERCASE IN YOUR JS CODE OR THE CODE WILL FAIL"

Sorting

To achieve sorting i added a click handler to each table header. Which calls buildTable() with the appropriate orderBy column passed in, as well as the number of the current page. In my html string each table header cell's id is set with the relevant database column name. By doing this i can use this.id as a parameter in the function and the id of the dom element that the event originates from will be automatically passed into the function. In this case the id of each dom elemen (table header cell) will be a database column name, thereby allowing the recieving CFC to use this value in the order by clause of its sql code

Page Links

Building the page links meant looping for the numberOfPages in the archive, on each loop building a span tag for each page link, giving each tag a click handler that calls buildTable(pageNumber,sortByOrder) each time it is clicked, with the currentPage number and current sorting order as parameters. So that each time one of these links is clicked JS builds a new table with the appropriate records showing for the current page number, sorted in the order last selected by the user (or the default if none chosen). Changing the sort order while on a page will cause different records too be shown as you are reordering the record set.

Highlighting Current Page Link

By finding the span id of the current page (which is the value of the global pageNumber) i could simply use addClass() to highlight the selected link

Next and Previous Links

The next and previous links were created and added to the pager div. Each of them has an event handler that prevents the default event of hyperlink occuring, and calls buildTable() on each click. Finally i get a reference to the table that's built at the start, and insert the page links before and after the table (HTML String).

Finally i insert the page links both before and after HTML string (table).

The HTML on .cfm page

This is simply 2 divs that are used by the JS to update the page, and a css class to highlight the selected link.

<html>
<head>
<title>Jquery Pagination</title>
<script src="jquery.js" type="text/javascript"></script>
<script src="paginationRealData.js" type="text/javascript"></script>
<style type="text/css">

.selectedLink { background-color: #CCFFFF; font-family:Arial, Helvetica, sans-serif; font-size:12px; border: medium solid; border-color:#000066; border-width: thin }
.notSelectedLinks { font-family:Arial, Helvetica, sans-serif; font-size:12px; }
</style>
</head>
<body>
<div id="displayDiv" align="center"></div>
<div id="queryData" align="center"></div>
</body>
</html>

Pagination in Coldfusion Model Glue Applications

Intro
I am describing here how i achieve pagination in my Model Glue apps. I hope the code is modular enough to be reused in other people's apps. You can see a running example here. The code mainly consists of a custom tag (cf_paginationMG) located in the view, and a coldfusion component (Pagination cfc) placed in the model. These both have straightforward APIs that can be easily wired via your controller. In your view, the event that generates the pagination can be labelled anything you like. As long as it calls a function you have defined in your controller that in turn invokes the pagination component. A seperate component is needed in the model that returns to the controller the actual record set that needs to be paged. You can see how i have done this in source code of my example.

Source Code of my example
Config Files
modelGlue.xml
coldspring.xml

The View
dspIndex.cfm

The controller

The Model
Pagination Component
Archive Component

Download a zip of the source code here

The View cf_paginationMG
This is where the meat and bones of the work is carried out. The code is wrapped in a custom tag (cf_paginationMG) called from a view template. (see my example view code)

The tag outputs the page numbered links (1,2,3,4,5,6... etc) as well as the 'next' 'previous' links which allow a user to page through an archive/record set. Having all the code for this in a custom tag makes it much easier to position the final pagination output above and below the output of your record set.

How to display the actual records / content is left up to you. The values returned from the Pagination component's getFromRecord() and getToRecord() methods dictate what particular records can be displayed for the current request. (see my example view code)

The Pagination component's getNumberOfPages() method returns a value that should be used as the value for the numberOfPages parameter of the custom tag.

cf_paginationMG parameters * are required

  • * numberOfpages: The number of pages your archive will contain. On each request the value for this parameter is returned by the model pagination method getNumberOfPages()
  • * numberOfRecordsOnPage: Determines the number of records that will be show from the record set/archive on a page at any one time. This value can be set (globally) in a coldspring config bean.
  • * event: the model glue event (in the case of my example getArchive) that fires each time a user clicks a numbered page link, or 'next' 'previous' link. This event generates the pagination on each request.
  • numberlinkClass: Take a CCS class that will determine how you want the page link numbers to look. Defaults to browser default.
  • nextPreviouslinkClass: Take a CCS class that will determine how you want the next previous links to look. Defaults to browser default.
  • currentPageNumberClass: Take a CCS class that will determine how you want the page number of the currently being viewed page to be displayed (highlighted). Defaults to browser default.

The Model
Pagination.cfc

The value returned from getNumberOfPages() should be used as the value for the numberOfpages parameter in the paginationMG custom tag. On each request from a user, the values returned by getFromRecord() and getToRecord() should be used in the view to help determine what particular records are to be displayed from the record set for that specific request. (see example controller view code)

Pagination.cfc parameters

  • currentPageNumber: the number of the page in the archive that is currently being displayed on the view. This defaults to 1.
  • recordsInArchive: the total number of records that you want to page through. i.e the record count of the record set (in my example this record set is returned from archive.cfc) to be paged through. Your record set's actual content is output independently of the custom tag using a query loop of the record set returned from archive.cfc, and the fromRecord, toRecord values from returned from the pagination methods getFromRecord() and getToRecord().
  • numberOfRecordsOnPage: the number of archive page links that will be displayed at the bottom/top (both) of your archives view

archive.cfc
This can be built by you. It should simply return record the records you want to page through. As you can see in the controller code of my example the record set's record count value is used to set the value of the recordsInArchive parameter of the Pagination component.

cf_MagicPager: Updated.

I have updated cf_MagicPager See its previous entry. It now has 3 added parameters that allow CSS classes to be added to style the look of the output. Download sample code, and docs in a zip here.

View running example of the below source code here

<!---change the appearance (colours/fonts/etc) by modifying these styles. Or add your own. --->
<style type="text/css">
.numberlinkClass {color:#0080ff;font-family:courier;font-weight:normal;font-size:large;}
.currentPageNumberClass {color:#0000ff;font-family:courier;font-weight:normal;font-size:large;}
.nextPreviouslinkClass {color:#000000;font-family:impact;font-weight:normal;font-size:x-large;}
</style>

<cfoutput>
<!---creates a query with some test data. This process would be best done in a cfc but is shown
this way for easy demo purposes.

Your cfc should return a record set and the two variables needed by the the tag to function, in a
structure. i.e NumberOfLinksOnPage and numberOfPages --->
   

<!--- Create a new three-column query, specifying the column data types --->
<cfset myQuery = QueryNew("recordNumber", "Integer")>
<!--- Make two rows in the query --->
<cfset newRow = QueryAddRow(MyQuery, 307)>
<cfloop from="1" to="307" index="i">
<!--- Set the values of the cells in the query --->
<cfset temp = QuerySetCell(myQuery, "recordNumber", #i#, #i#)>
</cfloop>

<!---These are the only 2 values that the tag needs to run. --->
<cfset variables.numberOfRecordsOnPage = 10>
<cfset variables.numberOfPages = #myQuery.recordCount# / #variables.numberOfRecordsOnPage#>

      <!---This is ensures page number default to number 1 when first called --->
<cfparam name="url.pageNumber" default="1" type="numeric">
      <!---here using url.pagenumber to work out what records to display on current page --->
      <cfset variables.from = ((#url.pageNumber# * #variables.numberOfRecordsOnPage#) - #variables.numberOfRecordsOnPage#) + 1>
    <cfset variables.to = (#url.pageNumber# * #variables.numberOfRecordsOnPage#)>
</cfoutput>

<P><b>Running cfMagicPager. Browsing through 307 records.</b></P>
<cf_magicPager
NumberOfPages="#variables.numberOfPages#"
numberOfRecordsOnPage="#variables.numberOfRecordsOnPage#"
numberlinkClass="numberlinkClass"
nextPreviouslinkClass="nextPreviouslinkClass"
currentPageNumberClass="currentPageNumberClass">

</cf_magicPager>

<P>
<!---dislay your records here --->
<cfloop query = "myQuery" startrow="#variables.from#" endrow="#variables.to#">
<CFOUTPUT><div align="center">Record Number: <b>#recordNumber#</b></div></CFOUTPUT><br>
</cfloop>
</P>


<cf_magicPager NumberOfPages="#variables.numberOfPages#"
numberOfRecordsOnPage="#variables.numberOfRecordsOnPage#"
numberlinkClass="numberlinkClass"
nextPreviouslinkClass="nextPreviouslinkClass"
currentPageNumberClass="currentPageNumberClass">

</cf_magicPager>

cf_MagicPager: Coldfusion custom tag

I have updated this tag. See this entry to get latest version.

cf_Magicpager is a Coldfusion custom tag that enables pagination. Pagination allows a website's users to navigate through a large record set in a user friendly way by generating page numbered links to each page in the archive. See example here

  • Previous and next links are also displayed to allow user to go back and forward one page at a time.
  • The pageNumber currently being viewed is highlighted a different color from the other pageNumbers.

The tag does not retrive and display records from a database .This is left up the developer to do in a seperate process. Giving flexibility as to what and how you display your records. In this respect the tag is very much an UI tag.An example of how records could be retrived and displayed is included in the code example in the docs.

The tag is designed to be linked to the displayed records using a variable called url.pageNumber. Each time a generated page number/link is clicked a variable called url.pageNumber is set with the page number.(Its defaults to one) Use this variable to help determine what records from your recordset (archive) should queryed and displayed on each page. E.G. If the number of the page currently being processed is 7 then you should seperately retrive and display records 70 to 79 from your recordset/archive.

Possible Tag Usage:
Place tag above or/and below where the records are output on template.

<!---CFC which generates your record set/ archive --->
<cfinvoke component="archive" method="getArchive" returnvariable="result">

<!---Calling magicPager to appear above record set --->

<cf_magicPager NumberOfPages="#result.numberOfPages#" NumberOfLinksOnPage="#result.NumberOfLinksOnPage#">
</cf_magicPager>


<!---Output your records here --->
<cfoutput query="result.records">
<p>
#title#<br>
#intro#<br>
<a href="fullArticle.cfm?articleId=#articleId#">Read more...</a><br>
</p>
</cfoutput>

<!---Calling magicPager to appear below record set --->
<cf_magicPager NumberOfPages="#result.numberOfPages#" NumberOfLinksOnPage="#result.NumberOfLinksOnPage#">
</cf_magicPager>

Attributes: (all are required) These values are best calculated alongside whatever code you use to retrive your database records. A cfc would be best, returning a structure that contains your database records and these values.

  • NumberOfPages: The number of pages you want your archive to have.This can be determined by dividing the record count value of a retrived record set by the number of records/articles you want to appear on any one page. e.g. 300 records retrived / 10 records on a page: this will give a figure of 30 pages. This is the amount of pages that will be needed to display the whole archive/record set.With each individual page being generated when a user clicks on its page number.
  • NumberOfPageLinks: The amount of page links to display on screen at any one time. This figure is doubled by the tag. e.g. If you enter 10 it will generate 20 links. 10 links before current page number, (if current page number is greater than 10) and 10 links after current page number (until there are less than 10 pages left to display in archive)

BlogCFC was created by Raymond Camden. This blog is running version 5.9.002. Contact Blog Owner