Woah there, what's this! A Javascript tutorial!? Yes, I know its been a while since a Javascript tutorial graced the pages of Switch On The Code - but what can I say? I kind of got bored with writing them. And so, after a few month hiatus (and a couple fresh ideas for tutorials), they are back.

So what is on the plate for today? Well, we are going to take a look at how to implement continuous pagination in javascript. What is continuous pagination, you ask? That's a very good question, and it's actually a little bit difficult to describe. Say you have a list with a large number of entries - much more than you would want to load and display at once. The general tactic would be to paginate them - display 10-20 at once, and then have the user click 'next' to see the next 20, and so on and so forth.

But if for the most part, the user is just quickly scanning through these entries, this can get annoying. The user has to click and wait for the next batch of entries to load every time that they are done with the current set. Continuous pagination gets rid of this problem. As the user approaches the end of the entries currently displayed, the next entries are requested asynchronously and appended to the end of the list displayed to the user. This way, the user never has to do anything directly to get the next entries - they are just always there waiting in the list when they are needed!

A great example of this behavior is on the site DZone (which, besides showing off the behavior I'm talking about, is a great developer resource - if you haven't been there, you should check it out).

But of course, we aren't here just to talk about someone else's implementation - we are here to write out own! Below, there is a small example that we are going to create today. As you scroll down to the bottom, it will go off, load more elements, and append them to the bottom.

Initial Entry 1
Initial Entry 2
Initial Entry 3
Initial Entry 4
Initial Entry 5
Initial Entry 6
Initial Entry 7
Initial Entry 8
Initial Entry 10
Initial Entry 11
Initial Entry 12
Initial Entry 13
Initial Entry 14
Initial Entry 15
Initial Entry 16
Initial Entry 17
Initial Entry 18
New Entry 1 of Set 1
New Entry 2 of Set 1
New Entry 3 of Set 1
New Entry 4 of Set 1
New Entry 5 of Set 1
New Entry 6 of Set 1
New Entry 1 of Set 2
New Entry 2 of Set 2
New Entry 3 of Set 2
New Entry 4 of Set 2
New Entry 5 of Set 2
New Entry 6 of Set 2
New Entry 1 of Set 3
New Entry 2 of Set 3
New Entry 3 of Set 3
New Entry 4 of Set 3
New Entry 5 of Set 3
New Entry 6 of Set 3

Ok, now that you're done having fun scrolling, we can drop right into the code. First, the basic html structure:

<div style="position:relative;height:400px;width:500px;border:1px solid black;">
  <div style="overflow:auto;height:100%;width:100%;"
      onscroll="OnDivScroll();" id="scrollContainer">
    <!-- Entries go here -->
  </div>
  <div id="loadingDiv" tyle="position:absolute;bottom:2px;right:20px;
      padding:5px;display:none;background-color:Black;color:White;">

    Loading...
  </div>
</div>

Really not much there - and the element we care about at the moment is the inner div, the 'scrollContainer'. It is a normal div, except that we have attached the method OnDivScroll to the event onscroll. As you might expect, the onscroll event is fired whenever the div is scrolled. So let's take a look at this OnDivScroll method:

function OnDivScroll()
{  
  var el = document.getElementById('scrollContainer');
  if(el.scrollTop < el.scrollHeight - 800)
    return;
 
 
  var loading = document.getElementById('loadingDiv');
  if(loading.style.display == '')
    return; //already loading
   
  loading.style.display = '';
 
  LoadMoreElements();
}

So first, we go and grab the scrollContainer element. Then we check the current scroll position against the total scrollable height of the element. The only oddity is that the property scrollTop gives the current scroll position in terms of the top of the element - which means that scrollTop can never be bigger than scrollHeight minus the actual height of the element. In this case, we are checking to see when scrollTop is within 800 pixels of scrollHeight. If it is, we are close enough to the bottom that we want to load more elements. This value of 800 is completely dependent on what you think feels right for your application - in this case, I just got the number by multiplying the actual height of the element by 2.

Once we know that we should load more elements, we grab ahold of the little loading div element. Then we check if it is currently being displayed or not. If it is being displayed, then we must have already dispatched a request for more elements in a previous OnDivScroll call. Otherwise, we display the loading div, and we call the function LoadMoreElements.

function LoadMoreElements()
{
  //Do a server callback to load
  //more elements
 
  setTimeout(LoadCallback, 350);
}

It is in this function that you would do an AJAX callback to request more elements from the server. Since this is just a little demo app (and I didn't want to write any server side code), we just simulate this action by using a setTimeout call. Why is this mostly equivalent to doing an actual callback? Well, an actual callback will take time, and occur asynchronously, and that is what the setTimout call here is doing for us.

So, after a bit of time, the server returns with some elements (or, in the case, the setTimeout executes our function):

var NumberOfNewEntrySets = 1;

unction LoadCallback()
{
  var el = document.getElementById('scrollContainer');
  var loading = document.getElementById('loadingDiv');
 
  loading.style.display = 'none';
 
  for(var i=1; i<=6; i++)
    el.innerHTML += "<div class='entry'>New Entry " + i
        + " of Set " + NumberOfNewEntrySets + "</div>";
  NumberOfNewEntrySets++;
}

In this case, again, since there is no actual server results, we are just generating new entries and adding them. Potentially, this callback would have an argument (the server response), and you would parse the response and add all the contained elements. Oh, and don't forget to set the loading div to be invisible again.

And there you go! That is all you need to do continuous pagination. Here is all the code together in a single block for your viewing pleasure:

<html>
  <head>
    <title>Auto Fill Scroll Test</title>
    <style type="text/css">
      .entry
      {
        height:75px;
 
        border-bottom:solid 1px #cccccc;
        border-top:solid 1px #cccccc;
        border-collapse:collapse;
        padding:5px;
      }
    </style>
    <script type="text/javascript" src="javascript.js"></script>
  </head>
  <body>
    <div style="position:relative;height:400px;width:500px;border:1px solid black;">
      <div style="overflow:auto;height:100%;width:100%;"
          onscroll="OnDivScroll();" id="scrollContainer">
        <div class="entry">Initial Entry 1</div>
        <div class="entry">Initial Entry 2</div>
        <div class="entry">Initial Entry 3</div>
        <div class="entry">Initial Entry 4</div>
        <div class="entry">Initial Entry 5</div>
        <div class="entry">Initial Entry 6</div>
        <div class="entry">Initial Entry 7</div>
        <div class="entry">Initial Entry 8</div>
        <div class="entry">Initial Entry 10</div>
        <div class="entry">Initial Entry 11</div>
        <div class="entry">Initial Entry 12</div>
        <div class="entry">Initial Entry 13</div>
        <div class="entry">Initial Entry 14</div>
        <div class="entry">Initial Entry 15</div>
        <div class="entry">Initial Entry 16</div>
        <div class="entry">Initial Entry 17</div>
        <div class="entry">Initial Entry 18</div>
      </div>
      <div id="loadingDiv" tyle="position:absolute;bottom:2px;right:20px;
          padding:5px;display:none;background-color:Black;color:White;">

        Loading...
      </div>
    </div>
  </body>
</html>

var NumberOfNewEntrySets = 1;

function OnDivScroll()
{  
  var el = document.getElementById('scrollContainer');
  if(el.scrollTop < el.scrollHeight - 800)
    return;
 
 
  var loading = document.getElementById('loadingDiv');
  if(loading.style.display == '')
    return; //already loading
   
  loading.style.display = '';
 
  LoadMoreElements();
}

function LoadMoreElements()
{
  //Do a server callback to load
  //more elements
 
  setTimeout(LoadCallback, 350);
}

function LoadCallback()
{
  var el = document.getElementById('scrollContainer');
  var loading = document.getElementById('loadingDiv');
 
  loading.style.display = 'none';
 
  for(var i=1; i<=6; i++)
    el.innerHTML += "<div class='entry'>New Entry " + i
        + " of Set " + NumberOfNewEntrySets + "</div>";
  NumberOfNewEntrySets++;
}

If you would like, you can download a simple html file with this example here. As always, fee free to leave any questions or comments below.

////////////////test code sample///////////////////

<script>
    if(true){
        jQuery(function(){
           var m = window.location.search.match(/p=(\d+)/);
           var curPage = m ? parseInt(m[1]) : 1;
           var loading = false;

           jQuery("#content").scroll(function(){
               if ( curPage >= 1 && !loading && this.scrollHeight -
                       this.scrollTop - this.offsetHeight < this.offsetHeight ) {
                   loading = true;
                   if ((25*curPage) < 78136) {
                        DWRUtil.setValue("scrollStats", 25*(curPage+1) + " of " + 78136);
                    } else {
                        DWRUtil.setValue("scrollStats", 78136);
                    }
                   $("progressIndicator").style.display="block";
                   jQuery.ajax({
                       type: "GET",
                       dataType: "html",
                       url: window.location + (window.location.search != '' ? "&" : "?") + "type=html&p=" + ( ++curPage ),
                       success: function( html ){
                           html = jQuery.trim( html );

                           if ( html ) {
                               jQuery("#content-inner").append( html );
                               
//                             }
                           } else
                               curPage = 0;
                       },
                       complete: function(){
                           loading = false;
//                         alert(window.location.pathname + (window.location.search != '' ? ""+ window.location.search +"&" : "?") + "p=" + ( curPage ))
                           pageTracker._trackPageview(window.location.pathname + (window.location.search != '' ? ""+ window.location.search +"&" : "?") + "p=" + ( curPage ));
                           _qacct="p-ebK_XdQH1HeLo"; quantserve();
                           $("progressIndicator").style.display="none";
                       }
                   });
               }
           });
        });
    }
</script>
posted on 2011-02-25 21:34  残荷斋  阅读(233)  评论(0)    收藏  举报