ColdFusion Pagination Made Easy

I know I haven’t posted in quite some time, but I’ve written a small method to help handle some of my day to day pagination challenges and I thought I’d share it. I’ve created a demo which uses MySQL, fake data, a ColdFusion customTag and the pagination method.

I imagine there’s quite a few developers that have already created something like this for themselves, so I apologize for being redundant if so. This is for the people who are constantly copying and pasting the same code over and over again throughout their applications. Not only that, but for anyone who really struggles to understand and implement pagination, this is great for you.

I won’t waste any of your time, so here’s a link to the demo if you want to skip the text and start testing it out already.

For the others that have stayed, I’m going to break down how to use it. Let’s start off with the a query. I’m using MySQL pagination, so it’s bit less involved than an RDBMS like Oracle. For anyone that needs assistance with implementing pagination with Oracle, please comment or send me an email and I’ll be happy to help out.

<cfset paginationArgs = StructNew() />
<cfset paginationArgs.rowsPerPage = 30 />
<cfset paginationArgs.page = url.page />
<cfset paginationArgs.urlString = cgi.QUERY_STRING />


<cfset start = paginationArgs.rowsPerPage * url.page />
<cfset start = start - paginationArgs.rowsPerPage />


<!--- query for some data and cache the query --->
<cfquery name="getAllMembers" datasource="local_sample">
	SELECT first_name,last_name,email,zip, (select count(*) from member) as totalCount
	FROM member
	LIMIT <cfqueryparam value="#start#" cfsqltype="cf_sql_integer" />,<cfqueryparam value="#paginationArgs.rowsPerPage#" cfsqltype="cf_sql_integer" />
</cfquery>

<cfset paginationArgs.totalCount = getAllMembers.totalCount />
<cfset paginationArgs.currentRecordCount = getAllMembers.recordCount />

You’ll notice that I had to do a bit of work before hand to get get the appropriate “start” value for the SQL query. This is merely the product of “the amount of rows per page you want to show” * “your current page” – “the amount of rows per page you want to show”. So if you’re showing 30 records a page and you’re on page 3, you’re looking at: ((30 * 3) – 30) = 60. This is telling the query that you want to start retrieving records 60 rows into the result set and you’re limiting it by 30 rows. This would mean you’re seeing rows 61-90. I’m willing to bet this is overly thorough, but better too much than too little.

You’ll notice that I’ve got a structure with some values going around that query, I’ll explain now with the pagination function.


<!----
//////////////////////////////////////////////////////////////////////////////////////////////////
	Filename: Paginate.cfc
	Purpose: To aggregate all of the basic pagination needs into one reusable function
	Change Log:
		04/22/2013 -  Michael Stone - Created
//////////////////////////////////////////////////////////////////////////////////////////////////		
---->
<cfcomponent>
	<cffunction name="createPagination" output="no" access="public" returntype="struct" hint="a generic method that handles the essential needs of pagination">
		<cfargument name="currentRecordCount" type="numeric" default="0" />
		<cfargument name="totalCount" type="numeric" default="0" />
		<cfargument name="rowsPerPage" type="numeric" default="10" />
		<cfargument name="page" type="numeric" default="1" />
		<cfargument name="maxPagesBefore" type="numeric" default="3" />
		<cfargument name="maxPagesAfter" type="numeric" default="3" />
		<cfargument name="urlString" type="string" default="" hint="This will typically just be cgi.query_string" />
		<cfargument name="pageVar" type="string" default="page" />
		<cfset local.paginationStruct = StructNew() />
		<cfset local.multiUrlParamsReplace = "" />
		
		<!--- let's first check to see if we should see the "&" or not for when we're doing the page number replace --->
		<cfif listLen(arguments.urlString,"&") GT 1>
			<cfdump var="#listLen(arguments.urlString,"&")#" />
			<cfabort />
			<cfset local.multiUrlParamsReplace = "&" />
		</cfif>
		
		<!--- get the number of pages --->
		<cfset local.paginationStruct["numberOfPages"] = Ceiling(arguments.totalCount/arguments.rowsPerPage) />
		<!--- start count --->
		<cfset local.paginationStruct["startCount"] = (((arguments.page-1) * arguments.rowsPerPage)+1) />
		<!--- end count --->
		<cfset local.paginationStruct["endCount"] = ((arguments.page-1) * arguments.rowsPerPage)+arguments.currentRecordCount />
		<!--- total count (obvious) --->
		<cfset local.paginationStruct["totalCount"] = arguments.totalCount / >

		<!--- create the next and previous links --->
		<cfif arguments.page LT local.paginationStruct["numberOfPages"]> 
			<cfset local.paginationStruct["nextLink"] = replace(arguments.urlString,"#local.multiUrlParamsReplace##arguments.pageVar#=#arguments.page#","")&"#arguments.pageVar#="&arguments.page+1 />
		</cfif>
		<!--- previous link --->
		<cfif arguments.page LTE local.paginationStruct["numberOfPages"] AND arguments.page GT 1 >
			<cfset local.paginationStruct["previousLink"] = replace(arguments.urlString,"#local.multiUrlParamsReplace##arguments.pageVar#=#arguments.page#","")&"#arguments.pageVar#="&arguments.page-1 />
		</cfif>
		
		<!--- handle the max before/after --->
		<cfset local.maxPages = arguments.maxPagesBefore + arguments.maxPagesAfter + 1 />
	
		<cfset local.startIndex = 1 />
		
		<cfset local.endIndex = local.paginationStruct["numberOfPages"] />
		 
		<cfif local.paginationStruct["numberOfPages"] GT local.maxPages>
	
			<cfset local.startIndex = arguments.page - arguments.maxPagesBefore />
			<cfset local.endIndex = arguments.page + arguments.maxPagesAfter />
	
			<cfif local.startIndex LT 1>
				<cfset local.startIndex = 1 />
				<cfset local.endIndex = (local.startIndex + local.maxPages) - 1 />
			</cfif>
	
			<cfif local.endIndex GT local.paginationStruct["numberOfPages"]>
				<cfset local.startIndex = local.paginationStruct["numberOfPages"] - local.maxPages />
				<cfset local.endIndex = local.paginationStruct["numberOfPages"] />
			</cfif>
		</cfif>	

		<!--- loop through to create the array of objects only if the endIndex is GT 1 --->
		<cfif local.endIndex GT 1>
			<cfset local.displayLinks = ArrayNew(1) />
			
			<cfloop from="#local.startIndex#" to="#local.endIndex#" index="local.i">
				<cfset local.pageObj = StructNew() />
				<cfset local.pageObj["pageNumber"] = local.i />
				<cfset local.pageObj["pageLink"] = "?"&replace(arguments.urlString,"#local.multiUrlParamsReplace##arguments.pageVar#=#arguments.page#","")&"#arguments.pageVar#="&local.i />
				<cfif arguments.page EQ local.i>
					<cfset local.pageObj["isCurrentPage"] = true />
				<cfelse>
					<cfset local.pageObj["isCurrentPage"] = false />
				</cfif>
				
				<cfset ArrayAppend(local.displayLinks,local.pageObj) />
			</cfloop>				
			
			<cfset local.paginationStruct["displayLinks"] = local.displayLinks />
		</cfif>
			
		<cfreturn local.paginationStruct />		
	</cffunction>
</cfcomponent>

I’m not going to go into detail on what exactly everything does, but more of how you use it.

Remember that structure I was populating? We’re now going to instantiate the pagination class and then pass in that structure to the method.

<!--- instantiate the pagination object --->
<cfset pagination = new paginate() />

<cfset paginate = pagination.createPagination(argumentCollection = paginationArgs) />

When you dump the paginate variable, you should see something like this:

ColdFusion Pagination Made Easy example

Let’s jump into using this new structure returned from the createPagination method. It’s really just a matter of outputting the variables and then figuring out your display.

 

I went the customTag route when displaying my pagination because I’m going to show it on the top of the table and the bottom.

Let’s jump into using this new structure returned from the createPagination method. It’s really just a matter of outputting the variables and then figuring out your display.

I went the customTag route when displaying my pagination because I’m going to show it on the top of the table and the bottom.

Here’s what the customTag looks like:

<cfparam name="attributes.paginationObj" default="" />

<cfif !IsStruct(attributes.paginationObj)>
	<cfthrow message="You're attempting to use the pagination customTag without passing in a formatted pagination struct." />
	<cfexit method="exittemplate" />
<cfelse>
	<cfset paginate = attributes.paginationObj />
</cfif>

<cfif ThisTag.ExecutionMode EQ "Start">
	<cfoutput>
		<p class="paginationCont">
			<!--- previous button --->
			<cfif StructKeyExists(paginate,"previousLink")>
				<a href="?#paginate.previousLink#" class="left">&laquo; Previous</a>
			</cfif>
		
			<cfif StructKeyExists(paginate,"displayLinks")>
				<cfloop from="1" to="#ArrayLen(paginate.displayLinks)#" index="i">
					<cfset thePage = paginate.displayLinks[i] />
					<cfif thePage.isCurrentPage>
						<span class="pagingNumber pagingHot">#thePage.pageNumber#</span>
					<cfelse>
						<a href="#thePage.pageLink#" class="pagingNumber" title="Go to page #thePage.pageNumber#">#thePage.pageNumber#</a>
					</cfif>
				</cfloop>
			</cfif>
		
		
			<!--- next button --->
			<cfif StructKeyExists(paginate,"nextLink")>
				<a href="?#paginate.nextLink#" class="right">Next &raquo;</a>
			</cfif>		
		<p>	
	</cfoutput>
</cfif>

The previous and next links are obvious, but in the middle you’ll see that I’m checking for the “displayLinks” key within the struct. This is an array of objects that contains your display of paging and current page. The links are already handled for you within the method and all you need to do is output the key value as shown with #thePage.pageNumber#. This is just to display the page numbers like Google, Bing or any other popular search engine. By default you have 3 numbers to your left and 3 numbers to your right, while having the active page selected in the middle. It’ll make more sense when you check out the demo, if you haven’t done so already.

You can overwrite just about everything for the method by changing an argument. You can have 4 numbers on each side just by passing in the following code before you’ve called the pagination method.

<cfset paginationArgs.maxPagesBefore = 4 />
<cfset paginationArgs.maxPagesAfter = 4 />

The additional methods are:

<cfargument name="currentRecordCount" type="numeric" default="0" />
<cfargument name="totalCount" type="numeric" default="0" />
<cfargument name="rowsPerPage" type="numeric" default="10" />
<cfargument name="page" type="numeric" default="1" />
<cfargument name="maxPagesBefore" type="numeric" default="3" />
<cfargument name="maxPagesAfter" type="numeric" default="3" />
<cfargument name="urlString" type="string" default="" hint="This will typically just be cgi.query_string" />
<cfargument name="pageVar" type="string" default="page" />

That sums it up for this ColdFusion pagination class. Please check out the demo and feel free to download the code and test it on your applications.

Tags: , , ,

2 Responses to "ColdFusion Pagination Made Easy"

Leave a Comment

Switch to our mobile site