Author image Ron Savage


Data::Page::Viewport - Scroll thru data a page, or just an item, at a time


This is a complete, tested, runnable program.


        use strict;
        use warnings;

        use Data::Page::Viewport;

        # -----------------------------------------------

        my(@data) = (qw/zero one two three four five six
        seven eight nine ten eleven twelve thirteen fourteen/);
        my($page) = Data::Page::Viewport -> new
                data_size => scalar @data,
                page_size => 4

        print "Data bounds: 0 .. $#data. \n";
        print "Data:        ", join(', ', @data), ". \n";
        print "Page bounds: 0 .. 3. \n";
        print "Page data:   ", join(', ', @data[0 .. 3]), ". \n";
        print "\n";


        for (-2, 1, 4, 4, 1, 3, 3, -2, 1, 2, 1, -4, -4,
                -1, 1, 2, -1, -2, -2, -1, -4, 4, 4, 4)
                print "Offset: $_. \n";

                @bound = $page -> offset($_) -> bounds();

                print "Page bounds: $bound[0] .. $bound[1]. \n";
                print 'Page data:   ',
                        join(', ', @data[$bound[0] .. $bound[1] ]),
                        ". \n";
                print '-' x 50, "\n";


Data::Page::Viewport is a pure Perl module.

This module keeps track of what items are on the 'current' page, when you scroll forwards or backwards within a data set.

Similarly to Data::Page, you can call sub offset(N), for + or - N, to scroll thru the data a page at a time.

And, like Set::Window, you can call sub offset(N), for + or - 1, to scroll thru the data an item at a time.

Clearly, N does not have to be fixed.

The viewport provides access to the 'current' page, and the code shifts indexes into and out of the viewport, according to the parameter passed to sub offset().

Note that the data is not passed into this module. The module only keeps track of the indexes within the viewport, i.e. indexes on the 'current' page.

You call sub bounds() on the object (of type Set::Window) returned by sub offset(), to determine what indexes are on the 'current' page at any particular point in time.

Also note that, unlike Set::Window, the boundaries of the viewport are rigid, so that changes to the indexes caused by sub offset() are limited by the size of the data set.

This means, if you do this:

        my($page) = Data::Page::Viewport -> new
            data_size => $#data,     # 0 .. $#data.
            page_size => $page_size, # 1 .. N.

        my(@bound) = $page -> offset(- 1) -> bounds();

the call to sub offset(- 1) will have no effect.

That is, when trying to go back past the beginning of the data set, the bounds will be locked to values within 0 .. data_size.

Similarly, a call which would go beyond the other end of the data set, will lock the bounds to the same range.

In short, you can't fall off the edge by calling sub offset().

This in turn means that the values returned by sub bounds() will always be valid indexes within the range 0 .. data_size.

The module implements this by building 2 objects of type Set::Window, one for the original data set (which never changes), and one for the 'current' page, which changes each time sub offset() is called (until the boundaries are hit, of course).

Note: No range checking is performed on the parameters to sub new().

Note: It should be obvious by now that this module differs from Data::Page, and indeed all such modules, in that they never change the items which are on a given page. They only allow you to change the page known as the 'current' page. This module differs, in that, by calling sub offset(+ or - N), you are effectively changing the items which are deemed to be on the 'current' page.


This module is available both as a Unix-style distro (*.tgz) and an ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.

See for details.

See for help on unpacking and installing each type of distro.

Constructor and initialization

new(...) returns a Data::Page::Viewport object.

This is the class's contructor.



This is the upper limit on the indexes controlled by the viewport.

The lower limit is assumed to be 0.

This parameter is mandatory.


This controls whether you get the old style or new style scrolling.

The two types of scrolling are explained next, assuming you have tied the up and down arrow keys to scrolling via user input, and assuming you highlight the 'current' record. This just makes it easier to explain.

Old style

The 1st record starts as the 'current' record, and the 1st down arrow causes the next record to become the 'current' record. All this is as expected.

However, in old style scrolling, that 1st down arrow key also causes the list of items to scroll upwards, so the 'current' record remains at the top of the current page.

New style

With new style scrolling, which I feel is more natural, the list does not begin to scroll upward until the 'current' record, and the highlight, reach the bottom of the page.

That is, the 'current' record, and the highlight, move down the page each time the down arrow key is hit, but the list of items which are displayed on the current page does not change, until the point where a down arrow would select a 'current' item not visible on the current page. At that point, the list of items visible on the current page begins to scroll up, leaving the 'current' record, and the highlight, at the last item on the current page.

The up arrow key does the same thing at the top of the page.

The default is 0, meaning you get the new style scrolling.

Set it to 1 to get the old style scrolling.

This parameter is optional.


This is the number of items on a page.

This parameter is mandatory.

For example, if you use this module in a program which accepts input from the user in the form of the PgDn and PgUp keys, for instance, you could just call sub offset(+ or - $page_size), to allow the user to scroll forwards and backwards thru the data a page at a time.

But, if you want to allow the user to scroll by using the up and down arrow keys, then these keys would result in calls like sub offset(+ or - 1).

Mathod: current()

The module keeps track of a 'current' item within the current page, and this method returns the index of that 'current' item.

The value returned will be in the range 0 .. data_size.

This means that when you hit the down arrow, say, and call offset(1), and the items on the current page do not change because the items at the end of the data set were already visible before down arrow was hit, then you can still call current() after each hit of the down arrow key, and the value returned will increase (up to data_size), to indicate which item, among those in the current window, is the 'current' item.

You could use this to highlight the 'current' item, which would change each time the down arrow was hit, even though the current page of items was not changing (because the final page of items was already being displayed).

Modules on CPAN which Manipulate Sets

There are quite a few modules on CPAN which provide scrolling capabilities, and one or more even allow you to have pages of different sizes, but none seem to allow for scrolling by anything other than a rigidly-fixed page size. This module does offer such flexibility, by allowing you to scroll backwards or forwards by any number of items, with differing step sizes per scroll.


There may be others. After all, CPAN is the man (with apologies to feminists ;-).


Data::Page::Viewport was written by Ron Savage <> in 2004.

Home page:


Australian copyright (c) 2004, Ron Savage.

        All Programs of mine are 'OSI Certified Open Source Software';
        you can redistribute them and/or modify them under the terms of
        The Artistic License, a copy of which is available at: