Welcome to AspAdvice Sign in | Join | Help

Alessandro Gallo

.NET & Beyond
Building a Pager control with the Atlas framework

Due to a requirement of a project I'm working on and to some threads in the ASP.NET Forums regarding client-side pagination, I've decided to implement a classic and well-known pagination logic using the Atlas framework. Please check also this post for general Atlas coding guidelines.

How it works
The Pager control exposes three properties:

  • pageSize, get/set the number of records per page.
  • totalRecords, get/set the number of records in the data source.
  • pageIndex, get/set the current page.
and raises a pageChanged event whenever the selected page index changes. When the event is raised, the event handler receives a PagerEventArgs instance that exposes the following methods:
  • get_pageSize()
  • get_totalRecords()
  • get_pageIndex()
The Pager is implemented as a standalone Atlas control; this means that it doesn't need to be associated with a particular control or data source (try to declare it on a page, set its totalRecords and pageSize properties and it works).

 

Usage
While the purpose of a Pager control is very clear (yes, display only a number of records at a time) it can be used to navigate through an already populated data source one page at a time, but also to populate the data source on demand, i.e. with the records corresponding to the current page selected by the user. I find the second approach very interesting because it allows to combine the UI benefit brought by the Pager with a "smaller" data structure, and this usually means a gain in peformance.
Actually I wrote the Pager to implement this approach, thus my suggestion is to bind a data control (for example, a Web.UI.ListView) to a "paged" data source, i.e. a Web.Data.DataSource control populated with the records for the current page (the data source could be populated in the pageChanged event handler).

Finally, here's the code for the Pager. Obviously it can (must) be optimized (e.g. look at those string concatenations) and personalized. It's a bit longer than the preceeding examples, but I've inserted comments to make it less annoying to read.

Pager.js

Type.registerNamespace('AtlasNotes');

AtlasNotes.Pager = function(associatedElement) {
    AtlasNotes.Pager.initializeBase(this, [associatedElement]);
    
    // Private Members.
    var _pageSize;
    var _pageIndex;
    var _totalRecords;
    
    // Properties.
    this.get_pageIndex = function() {
        return _pageIndex;
    }
    this.set_pageIndex = function(value) {
        if(value != _pageIndex) {
            _pageIndex = (value < 0) ? 0 : value;
            this._render();
            this.raisePropertyChanged('pageIndex');
        }
        this._raisePageChanged();
    }
    this.get_pageSize = function() {
        return _pageSize;
    }
    this.set_pageSize = function(value) {
        if(value != _pageSize) {
            _pageSize = (value <= 0) ? 10 : value;
            this._render();
            this.raisePropertyChanged('pageSize');
        }
    }
    this.get_totalRecords = function() {
        return _totalRecords;
    }
    this.set_totalRecords = function(value) {
        if(value != _totalRecords) {
            _totalRecords = (value < 0) ? 0 : value;
            this._render();
            this.raisePropertyChanged('totalRecords');
        }
    }
        
    // Events.
    // The Pager exposes the pageChanged event, which is raised
    // when the page index to be displayed changes.
    this.pageChanged = this.createEvent();
    
    // Initialize / Dispose.
    // These two methods are responsible for initializing/disposing
    // the control. Notice how both the corresponding base class methods are
    // called while performing cleanup.
    this.initialize = function() {
        AtlasNotes.Pager.callBaseMethod(this, 'initialize');
        
        if(!_pageSize) {
            _pageSize = 10;
        }
        if(!_pageIndex) {
            _pageIndex = 0;
        }
        if(!_totalRecords) {
            _totalRecords = 0;
        }
    }
    this.dispose = function() {
        
        AtlasNotes.Pager.callBaseMethod(this, 'dispose');
    }
    
    // Type Descriptor.
    // The type descriptor allows to specify which properties, methods
    // and events are exposed to declarative script usage.
    this.getDescriptor = function() {
        var td = AtlasNotes.Pager.callBaseMethod(this, 'getDescriptor');

        td.addProperty('pageIndex', Number);
        td.addProperty('pageSize', Number);
        td.addProperty('totalRecords', Number);
        td.addEvent('pageChanged', true);
       
        return td;
    }
    
    // Methods.
    // This method is responsible for the control rendering. It computes the
    // Pager's bounds, displays statistics on pages and records and useful
    // navigation links (first, last, previous, next, plus a range of page
    // numbers.
    this._render = function() {
    
        this._clearPager();
               
        var totalPages = this.getTotalPages();
                
        var hasPages = (totalPages > 1);
        
        if(hasPages && (_pageIndex > totalPages - 1)) {
            _pageIndex = (totalPages - 1);
        }

        var hasFirst = (_pageIndex >= 3) && (totalPages > 5);
        var hasLast = (_pageIndex + 3 < totalPages) && (totalPages > 5);
        var hasPrevious = (_pageIndex > 0)
        var hasNext = (_pageIndex < totalPages - 3);
        
        var lowerBound = (totalPages <= 5) || (_pageIndex <= 1) ? 0
                       : (_pageIndex < (totalPages - 2)) ? (_pageIndex - 2)
                       : (_pageIndex == (totalPages - 2)) || (_pageIndex == (totalPages - 1)) ? (totalPages - 5)
                       : 0;
                       
        var upperBound = (totalPages < 5) ? totalPages
                       : (_pageIndex == 0) ? 5
                       : (_pageIndex == 1) ? (_pageIndex + 4)
                       : (_pageIndex < (totalPages - 2)) ? (_pageIndex + 3)
                       : (_pageIndex == (totalPages - 2)) ? (_pageIndex + 2)
                       : (_pageIndex == (totalPages - 1)) ? (_pageIndex + 1)
                       : 0;
                                      
        if(hasPages) {
            var span = document.createElement('SPAN');
            span.innerHTML = 'Page ' + (_pageIndex + 1) + ' of ' + totalPages + ' ';
            this.element.appendChild(span);
            
            if(hasFirst) {
                this._addNavigationLink('&lt;&lt;', 0, 'Go to first page');
            }
            
            if(hasPrevious) {
                this._addNavigationLink('&lt;', _pageIndex - 1, 'Go to previous page');
            }
            
            var i = (lowerBound <= 0) ? 0 : lowerBound;
            
            for(i; i < upperBound; i++) {
                this._addNavigationLink(i + 1, i, 'Go to page ' + (i + 1));
            }
            
            if(hasNext) {
                this._addNavigationLink('&gt;', _pageIndex + 1, 'Go to next page');
            }
            
            if(hasLast) {
                this._addNavigationLink('&gt;&gt;', totalPages - 1, 'Go to last page');
            }
        }
    }
    // This method clears the Pager UI.
    this._clearPager = function() {
        this.element.innerHTML = '';
    }
    // This method allows to add a navigation link to the Pager. Each link
    // can have an associated tooltip.
    this._addNavigationLink = function(text, pageIndex, tooltip) {
        var a;
        
        if(pageIndex == _pageIndex) {
            a = document.createElement('SPAN');
            a.innerHTML = '<b>' + text + '</b>';
        }
        else {
            a = document.createElement('A');
            a.href = '#';            
            a.innerHTML = text;       
            a.onclick = Function.createDelegate(this, this._onPageClick);
            a.title = tooltip;
       }
       a._pageIndex = pageIndex;
       a.style.margin = '2px';
    
       this.element.appendChild(a);
    }
    // This method returns the total pages based on the number of records
    // and the page size.
    this.getTotalPages = function() {
        var tot = 0;
        
        if(_totalRecords) {
            if(_totalRecords == 0) {
                return 0;
            }
            
            tot = Math.floor(_totalRecords / _pageSize);
            
            if((_totalRecords % _pageSize) > 0) {
                tot++;
            }
        }
        return tot;
    }
    
    // Event Handlers.
    this._onPageClick = function(e) {
        _pageIndex = (e) ? e.target._pageIndex : window.event.srcElement._pageIndex;
        this._render();
        this._raisePageChanged();
    }
    this._raisePageChanged = function() {
        this.pageChanged.invoke(this, new AtlasNotes.PagerEventArgs(_pageIndex, _pageSize, _totalRecords));
    }
}
// Register our class with the framework.
AtlasNotes.Pager.registerSealedClass('AtlasNotes.Pager', Sys.UI.Control);
// Register our control to be used in declarative script (the Atlas markup).
Sys.TypeDescriptor.addType('script', 'pager', AtlasNotes.Pager);

// PagerEventArgs.
// When the pageChanged event is raised, the Pager passes to the event handler an instance
// of the PagerEventArgs class. This class exposes methods to retrieve the current page index,
// page size and total records.
AtlasNotes.PagerEventArgs = function(pageIndex, pageSize, totalRecords) {
    var _pageIndex = pageIndex;
    var _pageSize = pageSize;
    var _totalRecords = totalRecords;
    
    this.get_pageIndex = function() {
        return _pageIndex;
    }
    this.get_pageSize = function() {
        return _pageSize;
    }
    this.get_totalRecords = function() {
        return _totalRecords;
    }
    
    // This is the Sys.ITypeDescriptorProvider implementation.
    this.getDescriptor = function() {
        var td = new Sys.TypeDescriptor();
        
        return td;
    }
}
AtlasNotes.PagerEventArgs.registerSealedClass('AtlasNotes.PagerEventArgs', null, Sys.ITypeDescriptorProvider);

Example
Maybe I'll post a full example in another blog entry (because this post is already too long, sorry), but here's an example of a Pager declaration:

<pager id="myPager"
       pageSize="10"
       totalRecords="357"
       pageIndex="5"
       pageChanged="onPageChanged"
       />

UPDATES
06/22/2006 - Pager.js, updated the code to the Atlas CTP and fixed some bugs in the pager logic.
03/18/2006 - Pager.js, fixed some issues with the rendering logic.

Posted: Thursday, March 16, 2006 1:15 PM by Garbin

Comments

Callam said:

Hi Garbin,

This is great stuff! It feels like i'm learning a lot.. :) thank you for all your help!

I am still new to all this, i would be very interested to see the final full example (like the contextMenu) of how this pager is implemented with DataSource controls..

Thank you! - Cal
# March 16, 2006 2:59 PM

Atlas notes said:

A couple of days ago I've completed the first release of an Atlas-based mashup that you can find here....
# May 4, 2006 3:02 PM

Joel Rumerman said:

Hi Garbin,

It's funny, but I keep coming back to this example as a reference point for building a custom Atlas control even though what I'm working on has nothing to with paging. I know it's not up-to-date (March CTP), but it really does present a whole bunch of useful ideas.

Thx!
# June 8, 2006 2:05 PM

Garbin said:

Joel, I'm very happy to hear that and you're right, the sample is old. I will replace it soon with the new version (btw, you can find it in my mashup's source code). Thank you.
Garbin
# June 12, 2006 4:32 AM

Atlas notes said:

In the previous post I've showed a classic Pager control implemented with the Atlas framework. Now it's...
# June 22, 2006 6:03 AM

zitiger said:

Hi, thanks for your great work.

When I use your code, I found that it works well in FireFox, but in IE 6.0 there is a js error when page is loaded.

Can you give me some comments?
My Email : BugTiger@hotmail.com

Thanks.
# July 28, 2006 10:59 AM

zitiger said:

Hi, thanks for your great work.

When I use your code, I found that it works well in FireFox, but in IE 6.0 there is a js error when page is loaded.

Can you give me some comments?
My Email : BugTiger@hotmail.com

Thanks.
# July 28, 2006 11:00 AM

zitiger said:

Hi, thanks for your great work.

When I use your code, I found that it works well in FireFox, but in IE 6.0 there is a js error when page is loaded.

Can you give me some comments?
My Email : BugTiger@hotmail.com

Thanks.
# July 28, 2006 11:02 AM

ATLAS Forum Posts said:

All, I'm attempting to use the Pager control documented here: http://aspadvice.com/blogs/garbin/archive/2006/03/16/15813.aspx
# August 17, 2006 8:13 PM

Alessandro Gallo said:

A couple of days ago I&#39;ve completed the first release of an Atlas-based mashup that you can find

# September 6, 2008 12:47 AM
New Comments to this post are disabled