Created
March 25, 2014 11:13
Creating A Bidirectional Infinite Scroll Page With jQuery And ColdFusion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html> | |
<head> | |
<title>Bidirectional Infinite Scroll With jQuery And AJAX</title> | |
<style type="text/css"> | |
div.list-chunk { | |
padding-bottom: 1px ; | |
} | |
div.list-item { | |
border: 4px solid #E0E0E0 ; | |
margin: 0px 0px 13px 0px ; | |
padding: 10px 10px 10px 10px ; | |
} | |
div.list-item a.thumbnail { | |
float: left ; | |
height: 150px ; | |
margin: 0px 15px 10px 0px ; | |
width: 100px ; | |
} | |
div.list-item a.thumbnail img { | |
border: 1px solid #999999 ; | |
display: block ; | |
} | |
div.list-item h4 { | |
font-size: 18px ; | |
margin: 0px 0px 12px 0px ; | |
} | |
div.list-item div.offset { | |
border-top: 1px solid #CCCCCC ; | |
clear: both ; | |
font-size: 11px ; | |
padding-top: 4px ; | |
} | |
</style> | |
<script type="text/javascript" src="../jquery-1.4a2.js"></script> | |
<script type="text/javascript"> | |
// I get more list items and either prepend them or append | |
// them to the list depending on the target area. | |
function getMoreListItems( | |
container, | |
targetArea, | |
onComplete | |
){ | |
// Check to see if there is any existing AJAX call | |
// for the list data items. If there is, we want to | |
// return out of this method - no reason to overload | |
// the server with extraneous requests (more so than | |
// an infinite scroll effect already does!!). | |
if (container.data( "xhr" )){ | |
// Let the active AJAX request complete. | |
return; | |
} | |
// Get the min and max offsets of the current | |
// container. | |
var minOffset = (container.data( "minOffset" ) || 0); | |
var maxOffset = (container.data( "maxOffset" ) || 0); | |
// The count of list items to load per AJAX request. | |
// We are calling it a "chunk" size because each | |
// list chunk will be stored in its own sub-container | |
// to make DOM manipulation easier. | |
var chunkSize = 3; | |
// Check our target area to see what our next offset | |
// for loading should be. | |
if (targetArea == "top"){ | |
// We are prepending list items. | |
var nextOffset = (minOffset - 1 - chunkSize); | |
} else { | |
// We are appending list items. | |
var nextOffset = (maxOffset + 1); | |
} | |
// Launch AJAX request for next set of results and | |
// store the resultant XHR request with the container. | |
container.data( | |
"xhr", | |
$.ajax({ | |
type: "get", | |
url: "./bidirectional.cfm", | |
data: { | |
offset: nextOffset, | |
count: chunkSize | |
}, | |
dataType: "json", | |
success: function( response ){ | |
// Apply the response to the container | |
// for the given target area. | |
applyListItems( container, targetArea, response ); | |
}, | |
complete: function(){ | |
// Remove the stored AJAX request. This | |
// will allow subsequent AJAX requests | |
// to execute. | |
container.removeData( "xhr" ); | |
// Call the onComplete callback. | |
onComplete(); | |
} | |
}) | |
); | |
} | |
// I apply the given AJAX response to the container. | |
function applyListItems( container, targetArea, items ){ | |
// Get a reference to our HTML template for a new | |
// list item. | |
var template = $( "#list-item-template" ); | |
// Create an array to hold our HTML buffer - this will | |
// be faster than creating individual DOM elements and | |
// appending them piece-wise. | |
var htmlBuffer = []; | |
// Loop over the array to create each list element. | |
$.each( | |
items, | |
function( index, item ){ | |
// Modify the template and append the result | |
// to the HTML buffer. | |
htmlBuffer.push( | |
template.html().replace( | |
new RegExp( "\\$\\{(src|offset)\\}", "g" ), | |
function( $0, $1 ){ | |
// Return property. | |
return( item[ $1.toUpperCase() ] ); | |
} | |
) | |
); | |
} | |
); | |
// Create a list chunk which will hold our data. | |
var chunk = $( "<div class='list-chunk'></div>" ); | |
// Append the list item html buffer to the chunk. | |
chunk.append( htmlBuffer.join( "" ) ); | |
// Create the min and max offset of the chunk. | |
chunk.data( "minOffset", items[ 0 ].OFFSET ); | |
chunk.data( "maxOffset", items[ items.length - 1 ].OFFSET ); | |
// Check to see which target area we are adding the | |
// list items to (top vs. bottom). | |
if (targetArea == "top"){ | |
// Get the current window scroll before we update | |
// the list contente. | |
var viewTop = $( window ).scrollTop(); | |
// Prepend list items. | |
container.prepend( chunk ); | |
// Now that the chunk has been added to the page, | |
// it should have a height that can be calculated. | |
var chunkHeight = chunk.height(); | |
// Re-adjust the scroll of the window to make sure | |
// the user doesn't suddenly jump to a crazy place. | |
$( window ).scrollTop( viewTop + chunkHeight ); | |
// Now that we moved the list up, let's remove | |
// the last chunk from the list. | |
container.find( "> div.list-chunk:last" ).remove(); | |
} else { | |
// Append list items. | |
container.append( chunk ); | |
// Check to see if we have more chunks than we | |
// want (an arbitrary number, but enough to make | |
// sure we can comfortable fill the page). | |
if (container.children().size() > 3){ | |
// We want to remove the first chunk in the | |
// list to free up some browser memory. | |
// Get the current window scroll before we | |
// remove a chunk. | |
var viewTop = $( window ).scrollTop(); | |
// Get the chunk that we are going to remove. | |
var oldChunk = container.children( ":first" ); | |
// Get the height of the chunk we are about | |
// to remove. | |
var oldChunkHeight = oldChunk.height(); | |
// Remove the hunk. | |
oldChunk.remove(); | |
// Now, we need to ajust the scroll offset | |
// of the window so the user is not jumped | |
// around to a crazy place. | |
$( window ).scrollTop( viewTop - oldChunkHeight ); | |
} | |
} | |
// Now that we have updated the chunks in the | |
// container, let's update the min / max offsets of | |
// the container (which will be used on subsequent | |
// AJAX requests). | |
container.data( | |
"minOffset", | |
container.children( ":first" ).data( "minOffset" ) | |
); | |
container.data( | |
"maxOffset", | |
container.children( ":last" ).data( "maxOffset" ) | |
); | |
} | |
// I check to see if more list items are needed based on | |
// the scroll offset of the window and the position of | |
// the container. I return a complex result that not only | |
// determines IF more list items are needed, but on what | |
// end of the list. | |
// | |
// NOTE: These calculate are based ONLY on the offset of | |
// the list container in the context of the view frame. | |
// This does not take anything else into account (more | |
// business logic might be required to see if loading | |
// truly needs to take place). | |
function isMoreListItemsNeeded( container ){ | |
// Create a default return. This return value contains | |
// requirements for both the top and bottom of the | |
// content list. | |
var result = { | |
top: false, | |
bottom: false | |
}; | |
// Get the view frame for the window - this is the | |
// top and bottom coordinates of the visible slice of | |
// the document. | |
var viewTop = $( window ).scrollTop(); | |
var viewBottom = (viewTop + $( window ).height()); | |
// Get the offset of the top of the list container. | |
var containerTop = container.offset().top; | |
// Get the offset of the bottom of the list container. | |
var containerBottom = Math.floor( | |
containerTop + container.height() | |
); | |
// I am the scroll buffers for the top and the bottom; | |
// this is the amount of pre-top and pre-bottom space | |
// we want to take into account before we start | |
// loading the next items. | |
// | |
// NOTE: The top buffer is a bit bigger only to make | |
// the transition feel a bit *safer*. | |
var topScrollBuffer = 500; | |
var bottomScrollBuffer = 200; | |
// Check to see if the container top is close enough | |
// (with buffer) to the top scroll of the view frame | |
// to trigger loading more items (at the top). | |
if ((containerTop + topScrollBuffer) >= viewTop){ | |
// Flag requirement at top. | |
result.top = true; | |
} | |
// Check to see if the container bottom is close | |
// enought (with buffer) to the scroll of the view | |
// frame to trigger loading more items (at the | |
// bottom). | |
if ((containerBottom - bottomScrollBuffer) <= viewBottom){ | |
// Flag requirement at bottom. | |
result.bottom = true; | |
} | |
// Return the requirments for the loading. | |
return( result ); | |
} | |
// I check to see if more list items are needed, and, if | |
// they are, I load them. | |
function checkListItemContents( container ){ | |
// Check to see if more items need to be loaded at | |
// the top or the bottom (based purely on position). | |
// Returns: { top: boolean, bottom: boolean }. | |
var isMoreLoading = isMoreListItemsNeeded( container ); | |
// Define an onComplete method for the AJAX load that | |
// will call this method again to make sure there is | |
// always enough data loaded on the page. | |
var onComplete = function(){ | |
checkListItemContents( container ); | |
}; | |
// Check to see if more list items are needed at the | |
// top. If so, we will check to offsets to see if the | |
// load needs to take place. | |
// | |
// NOTE: Position is only *part* of how we determine | |
// if additional content is needed at the top. | |
if ( | |
isMoreLoading.top && | |
container.data( "minOffset" ) && | |
(container.data( "minOffset" ) > 1) | |
){ | |
// Load and prepend more list items. | |
getMoreListItems( | |
container, | |
"top", | |
onComplete | |
); | |
// Check to see if more list items are needed at the | |
// bottom. For this, all we are going to rely on is | |
// the offset of the container (since we can load | |
// ad-infinitum in the bottom direction). | |
} else if (isMoreLoading.bottom){ | |
// Load and append more list items. | |
getMoreListItems( | |
container, | |
"bottom", | |
onComplete | |
); | |
} | |
} | |
// -------------------------------------------------- // | |
// -------------------------------------------------- // | |
// When the DOM is ready, initialize document. | |
jQuery(function( $ ){ | |
// Get a reference to the list container. | |
var container = $( "#container" ); | |
// Bind the scroll and resize events to the window. | |
// Whenever the user scrolls or resizes the window, | |
// we will need to check to see if more list items | |
// need to be loaded. | |
$( window ).bind( | |
"scroll resize", | |
function( event ){ | |
// Hand the control-flow off to the method | |
// that worries about the list content. | |
checkListItemContents( container ); | |
} | |
); | |
// Now that the page is loaded, trigger the "Get" | |
// method to populate the list with data. | |
checkListItemContents( container ); | |
}); | |
</script> | |
</head> | |
<body> | |
<h1> | |
Bidirectional Infinite Scroll With jQuery And AJAX | |
</h1> | |
<div id="container"> | |
<!-- Content will be loaded here dynamically. --> | |
</div> | |
<!-- | |
This is the tempalte that will be used when adding the | |
list items to the DOM. It contains two different variable | |
place-holders: | |
${src} : The source of the image. | |
${offset} : The offset of the record. | |
--> | |
<script id="list-item-template" type="application/template"> | |
<div class="list-item"> | |
<a href="./${src}" target="_blank" class="thumbnail"> | |
<img src="./thumbs/${src}" width="100" height="150" /> | |
</a> | |
<div class="details"> | |
<h4> | |
${src} | |
</h4> | |
<p> | |
Pauline Nordin is a fitness figure professional | |
athlete, AST sports science spokesperson, | |
Fitness journalist, fitness model, TV | |
personality, Trainer and Nutrition coach, | |
Swedish Bodybuilding champion three years in a | |
row, Cover model Ironman magazine, Cover model | |
Body Magazine, fitness profile etc etc. | |
</p> | |
</div> | |
<div class="offset"> | |
Offset: ${offset} | |
</div> | |
</div> | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- Param the offset variable. ---> | |
<cfparam name="url.offset" type="numeric" default="1" /> | |
<cfparam name="url.count" type="numeric" default="30" /> | |
<!--- Create the return array. ---> | |
<cfset items = [] /> | |
<!--- | |
Loop over the count to populate the return array with | |
test data. In this case, test data is 1 of 5 randomly | |
selected thumbnail src values. | |
NOTE: We are including the offset of each record to | |
make the Javascript a bit easier (no offset calculations | |
will be required). | |
---> | |
<cfloop | |
index="index" | |
from="#url.offset#" | |
to="#(url.offset + url.count)#" | |
step="1"> | |
<!--- Create a random item. ---> | |
<cfset item = { | |
offset = index, | |
src = "girl#randRange( 1, 5 )#.jpg" | |
} /> | |
<!--- Append a random image name. ---> | |
<cfset arrayAppend( items, item ) /> | |
</cfloop> | |
<!--- Serialize the array. ---> | |
<cfset serializedItems = SerializeJSON( items ) /> | |
<!--- Convert it to binary for streaming to client. ---> | |
<cfset binaryItems = toBinary( toBase64( serializedItems ) ) /> | |
<!--- Set the content length. ---> | |
<cfheader | |
name="content-length" | |
value="#arrayLen( binaryItems )#" | |
/> | |
<!--- Stream binary content back as JSON. ---> | |
<cfcontent | |
type="application/x-json" | |
variable="#binaryItems#" | |
/> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment