That title really isn't very clear, so let me explain a bit more about what this blog entry is about. A reader asked if it was possible to do pagination via Ajax (ie, each page of content is loaded via Ajax), but have the controls (the previous and next buttons) exist outside of the Ajax-loaded content. This is an interesting question. It is incredibly trivial to load content via Ajax with jQuery. My seven year old could do it in his sleep. But we need to find a way to handle showing, and hiding, the navigation controls. We also need to ensure those controls can correctly "drive" the content to handle paging. Like ColdFusion, jQuery provides many ways to solve a problem. This is just one example.
Let's begin by creating the template that will serve our paged data. I'm calling this displayart.cfm. It will use the cfartgallery datasource installed with the ColdFusion examples.
<cfquery name="getart" datasource="cfartgallery">
select art.artname, art.description, art.price
from art
order by art.artname asc
</cfquery> <cfoutput query="getart" startrow="#url.start#" maxrows="#perpage#">
<p>
<b>#artname#</b> #dollarFormat(price)#<br/>
#description#
</p>
</cfoutput>
<cfparam name="url.start" default="1">
<cfset perPage = 10>
Nothing too fancy here. Notice I use url.start to drive where the pagination begins. Also note I'm doing "sloppy" paging with cfoutput startrow and maxrows. This isn't efficient, but it gets the job done. Now let's switch to the front end:
$(function() {
$("#content").load("displayart.cfm")
})
</script>
</head> <body> <h2>Art</h2> <div id="content"></div> <div id="nav">
<input type="button" id="prev" value="Previous">
<input type="button" id="next" value="Next">
</div> </body>
</html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
I've got a super simple page here with one div to hold my content and another div to hold my pagination controls. When the document loads, I immediately load in the my art content. So this works easily enough, but now we need to address our first issue - how do I get the controls to show or hide? I edited displayart.cfm and added the following:
<script>
$(function() {
<!--- Show Prev if start gt 1 --->
<cfif url.start gt 1>
<cfoutput>
showPrev()
</cfoutput>
<cfelse>
hidePrev()
</cfif>
<!--- Show next if we need to --->
<cfif (url.start + perPage - 1) lt getart.recordCount>
<cfoutput>
showNext()
</cfoutput>
<cfelse>
hideNext()
</cfif>
})
</script>
This code will execute when the content is loaded into the page via our main "controller" page. It checks to see if we are at the beginning of our pagination or not at the end, and either outputs a show or hide version for Prev and Next. So if we have more than one page of content, the net result on the first load should be:
hidePrev()
showNext()
Which reflects the fact that we don't need a Previous button, but we do need a next button. So I went back to my controller page (can't think of a better term for that - hopefully it makes sense) and added the following:
function hidePrev(){
$("#prev").hide()
} function showNext(){
$("#next").show()
} function hideNext(){
$("#next").hide()
}
function showPrev(){
$("#prev").show()
}
As you can see, I simply use jQuery's show and hide commands to set the visibility of each button. I ran my code and confirmed it worked. On the first load, the previous button was hidden and the next button showed up. Note that this runs after the first bit of content was loaded. That means the Previous button could be visible for a second or two. I don't think this is bad but if it bugs you, you can use CSS to hide the button initially, or simply add: $("#prev").hide() to the $(function() block.
Ok, so that's one problem solved. The next problem is - how do I get the buttons to actually work. It's easy enough to add an event handler for their click events, but they need to know how to update the page in the content div. I decided on the following modification. When I make the call to show the previous or next button, I pass along the proper starting index:
<!--- Show Prev if start gt 1 --->
<cfif url.start gt 1>
<cfoutput>
showPrev(#url.start-perpage#)
</cfoutput>
<cfelse>
hidePrev()
</cfif>
<!--- Show next if we need to --->
<cfif (url.start + perPage - 1) lt getart.recordCount>
<cfoutput>
showNext(#url.start+perpage#)
</cfoutput>
<cfelse>
hideNext()
</cfif>
Back in the main template, I updated showPrev and showNext to store these values:
function showPrev(newprevstart){
prevstart = newprevstart
$("#prev").show()
} ... function showNext(newnextstart){
nextstart = newnextstart
$("#next").show()
}
var prevstart = 1
var nextstart = 1
And finally, I added my event listeners:
$("#prev").click(function() {
$("#content").load("displayart.cfm?start="+prevstart);
})
$("#next").click(function() {
$("#content").load("displayart.cfm?start="+nextstart);
})
So basically we ended up with our Ajax loaded content telling the parent when to show or hide the navigation and also how to form the new URL to load the previous or next page of data. As I said earlier, I'm sure this could be done a thousand other ways! I'll end with the complete listings for both, starting with the main template:
$(function() {
$("#content").load("displayart.cfm") $("#next").click(function() {
$("#content").load("displayart.cfm?start="+nextstart);
}) $("#prev").click(function() {
$("#content").load("displayart.cfm?start="+prevstart);
}) }) var prevstart = 1
var nextstart = 1 function showPrev(newprevstart){
prevstart = newprevstart
$("#prev").show()
} function hidePrev(){
$("#prev").hide()
} function showNext(newnextstart){
nextstart = newnextstart
$("#next").show()
} function hideNext(){
$("#next").hide()
}
</script>
</head> <body> <h2>Art</h2> <div id="content"></div> <div id="nav">
<input type="button" id="prev" value="Previous">
<input type="button" id="next" value="Next">
</div> </body>
</html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
And here is displayart.cfm:
<cfquery name="getart" datasource="cfartgallery">
select art.artname, art.description, art.price
from art
order by art.artname asc
</cfquery> <script>
$(function() {
<!--- Show Prev if start gt 1 --->
<cfif url.start gt 1>
<cfoutput>
showPrev(#url.start-perpage#)
</cfoutput>
<cfelse>
hidePrev()
</cfif>
<!--- Show next if we need to --->
<cfif (url.start + perPage - 1) lt getart.recordCount>
<cfoutput>
showNext(#url.start+perpage#)
</cfoutput>
<cfelse>
hideNext()
</cfif>
})
</script> <cfoutput query="getart" startrow="#url.start#" maxrows="#perpage#">
<p>
<b>#artname#</b> #dollarFormat(price)#<br/>
#description#
</p>
</cfoutput>
<cfparam name="url.start" default="1">
<cfset perPage = 10>
Archived Comments
While I understand the need to keep things simple in order to show the syntax, I think this example could be developed a little further in order to show better ways to do some things. You're right in that there are 1000 different ways to accomplish the same thing, but some are clearly better than others.
For instance, putting the query inside a component, separating out the JavaScript into a .js file, using .ajax with json instead of .load (skip over .get and .post), error trapping, and using the disable attribute instead of hide().
I think I would liken this post to showing someone how gunpowder can be used for fireworks. JQuery is much more powerful if written using a little different syntax. It's "DYNOMITE"!
I played around with jQuery UI this morning, replacing the two input buttons with UI prev/next icons. Just another thought for another potential improvement.
Wow. Um, I strongly disagree. While I'm definitely pro-MVC, I do not agree that it makes sense to complicate this example by putting the query into a CFC. It adds nothing to the lesson being taught here. Ditto for moving the JS code into a separate file.
Now to your other points:
1) .ajax versus .load
Why? .load worked perfectly fine.
2) error trapping
Ok, I'll give you that. More error checking would be nice, but if it were discussed, it would still make sense to do it after the main part of this entry. I'm a big believer in iterative updates.
3) disable versus hide
That is 100% a UI decision. Not a performance or best practices decision. The original reader wanted to hide the buttons, so I did so.
As to your last statement about jQuery being much more powerful with different syntax... did I not get the job done? Sometimes it isn't about the number of lines of code or how obtuse but getting the task done. To me, it's like people who argue for compare() in CF because it runs a few ms faster, even though it's harder for folks to read.
Ray,
Please stop distracting everyone with useful content. We're trying to get some 'real' work done. ;-)
This work pefectly but it becomes slow with have database.
I believe if we write business logic in SQL/PSQL for paging that is the best way
@Nikhil - Right - and I spell that out specifically in the blog post. The focus here was on the Ajax technique, not the SQL.
Any reason why this wouldn't work in IE7? It works in Firefox, but not IE7. From what I can tell, IE won't even load the displayart.cfm page from the jQuery script. What am I doing wrong? Thanks.
Does IE report any errors?
IE7 is not reporting any errors.
Good post Ray, even wondered if we have some kind of javascript on the images like preloader that will fail,
This is such a simple and neat solution Ray. For some who is not very well versed with jQuery or ajax, this post is a gift. Thanks a lot!
Excellent solution to the original problem. Thanks to you, now I know how to deal with it. Thanks!