The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Class::DBI::Factory::List - an iterator-based retriever and paginator of Class::DBI data

SYNOPSIS

        $list = Class::DBI::Factory::List->from( $iterator );

    or
    
        $list = Class::DBI::Factory::List->new({
                 class => My::CD,
                 genre => $genre,
                 year => 1975,
                 startat => 0,
                 step => 20,
                 sortby => 'title',
                 sortorder => 'asc',
         });
         
         my @objects = $list->page;
         my $total = $list->total;

INTRODUCTION

Class::DBI::Factory::List (henceforth CDFL) is meant to do most the work of constructing, retrieving and displaying a list of Class::DBI objects. It is designed to be used in a template-driven application, and gives easy, readable shorthands for the different bits of information a template might want to display in the course of paginating and navigating a list.

CDFL is capable of constructing simple queries, but uses normal cdbi mechanisms as much as possible: the query is built with a call to search(), and iterators are used for all retrieval operations, for example. This means that we inherit the limitations of search(), and cannot build very complex queries within the module.

You can get around this limitation by using Class::DBI's other retrieveal methods or creating your own custom ones. So long as they return an iterator, you should be able to call:

CDFL->from( My::Class->complex_retrieval_method() );

CDFL is written as part of Class::DBI::Factory. There is no reason it could not be used in another context - the factory just makes it easier to get at - but this does mean that it has been designed first to play a role in a factory setting: it would probably have a cleaner interface if it had been built as an straight response to the problem of making lists, for example. Its development so far has been a gradual generalisation of what were initially very specific abilities.

At the moment the pagination routines are internal, but we intent to follow the example of Class::DBI::Pager and use Data::Page for that. Too many errors by one to avoid otherwise (though we're fencepost-clean at the moment and will hopefully stay that way).

BUILDING A LIST

There are two ways to construct a list object. You can build one from scratch by passing in a set of parameters and constraints, or you can turn an existing iterator into a list directly.

terminology: parameters and constraints

'Parameter' is used here, and in the code, to refer to a criterion that is used in selecting the members of the list: in other words, something that is going to end up in the WHERE clause of the search query around which this list is a wrapper. 'Constraint' is used to refer to a criterion that will govern the presentation of the list: the number of items to display per page, the order in which the list is sorted, and so on.

There are only four constraints, at the moment: anything else is assumed to be a query parameter. They are startat, step, sortby and sortorder, and the names are hopefully self-explanatory.

There is some muddling there, because the sorting constraints are also used to build the query - using the current, rather half-hearted cdbi mechanism to append an ORDER BY clause - but the pagination constraints are not used until the iterator is sliced. This is a consequence of using iterators, and will no doubt go through many more changes, so we're not letting it bother us too much.

The third kind of criterion is the content class, which is usually, sloppily, regarded as another parameter. As in all Class::DBI operations, it provides the database connection and table name.

In the absence of any parameters apart from the content class, the module functions as a simple pager for that class. If that's your only use of it, then Class::DBI::Pager is almost certainly a more efficient way to go.

new()

To build a list from scratch, you just need to supply a hashref of criteria that contains at least a 'class' parameter, which should be a Full::Class::Name. In this call:

        my $list = Class::DBI::Factory::List->new({
                 moniker => 'cd',
                 genre => $genre,
                 year => 1975,
                 startat => 0,
                 step => 20,
                 sortby => 'title',
         });

A quick call to the factory gives us a fully qualified class name for the moniker. The startat, step and sortby criteria are pulled out and stored as display constraints. The remaining criteria are held as the parameters which will be used passed to the $content_class->search() method when the time comes to display the list.

So if your application is using Class::DBI::Factory and the Template Toolkit, creating a list on page can be as simple as:

        [% list = factory.list(
                'cd',
                'artist' => artist,
                'sortby' => 'title',
        ) %]

from()

Building a list from an iterator is simpler but less flexible: all you have to do is pass in the iterator and CDFL will do the rest. No search parameters are needed, but you can still supply pagination constraints. There is also the option to supply a calling object, since one of the main uses of from() is to paginate a list of relations. Nothing is done to the supplied parent object: it is just held ready in case it is needed to build the list title (or a template asks for it).

my $iterator = $artist->cds; my $list = Class::DBI::Factory::List->from( $iterator, $artist, { step => 20 });

DISPLAYING A LIST

Execution of the list query is deferred until you call one of the content-display methods (unless you supplied the iterator up front, of course). They are:

page()

returns a simple array of inflated Class::DBI objects obtained by applying your pagination constraints to the iterator built from your query. You get a page full of list, in other words, which is created by calling iterator->slice(). Very simple.

total()

returns the number of records overall. The same as calling iterator->count.

title()

By default this is a rudimentary method that just returns the table name of the content class. It is intended to be subclassed to return a title in the form that suits your application. My data classes usually have a class_plural() method, and I tend to scan the parameters to append qualifications like 'by person x' or 'in project y'.

iterator()

you can also get at the iterator directly, if your requirements are more complicated than just an orderly segment of list.

POST-PROCESSING

Each item retrieved by contents() can be tweaked before it is returned. To accomplish this you just have to define a tweak_entry method in subclass. This is most useful for simple tasks like numbering the list entries.

tweak_entry()

receives each object in the contents() list before it is returned to the caller, along with the position of that object on the present page. It should return the object with which it has been supplied, unless you have decided that you want to exclude that one from the list (but note that the displayed totals won't be affected, so exclusion by this means might look odd).

For example, if you wanted to number each entry in your list according to its overall position, you could define a 'list_pos' TEMP column for every class and:

  sub tweak_entry {
        my ($self, $thing, $position) = @_;
        $position ||= 1;
        $position += $self->start;
        $thing->list_pos($position);
        return $thing;
  }

QUERY COMPONENTS

The main query-assembly routines are separated out here in order to facilitate subclassing. You can tweak the query construction here without changing the overall way things are done.

where()

returns a hashref of {column => value} which can be passed on to content_class->search().

order_by()

returns the sort_by field, with ' DESC' appended to it if necessary.

start()

returns the start_at value

end()

calculates and returns the end of the page based on start_at and step.

PAGINATION

The remaining methods are all to do with the tediously fiddly business of displaying pagination information and linking pages.

qs()

returns the query string that would be used to build the present page. Any supplied parameters are used to extend or override the present set, so:

  $list->qs( step => 50 );

Will give you the query string needed to display this list with 50 items per page instead of whatever the current value is.

next_qs()

returns the query string that would be used to build the next page

previous_qs()

returns the query string that would be used to build the previous page

make_qs()

Accepts a hash and returns it as a query string. Override this method if you would like to use a notation other than foo=bar;this=that.

prefix()

If a prefix parameter is supplied during construction, then all the pagination links returned will take the form "${prefix}parameter". This is to allow the separate pagination of more than one list on a page. If for some reason you want to set the prefix after list construction, just supply a value to this method.

as_form()

returns a set of hidden html inputs that could be used to create a form that would generate the present page. Any fields that you would like to omit from the form can be supplied as a list if, for example, you would like to allow users to enter a value for the year, but keep everything else the same:

  $list->as_form('year');

_as_hash()

This is the basis of all the as_* and *_qs methods: it returns a hash of parameter=>value, suitable for recreating this list object with whatever changes are required. It prepends the prefix marker to each key, and accepts a hash of override values.

show_pagination()

returns true if the total number of records exceeds the number to be displayed on each page.

sortable()

Returns true if this list can be resorted simply. The answer is yes if the underlying iterator was built here - we can build it again with a different sort clause - but we assume that it is no if the iterator was passed in to us already constructed.

previous_step()

returns the number of items that would be displayed on the previous page in the sequence.

previous_step()

returns the overall position at which the previous page in the sequence would start.

next_step()

returns the number of items that would be displayed on the next page in the sequence.

next_step()

returns the overall position at which the next page in the sequence would start.

ADMINISTRIVIA

Which just leaves a few routines that provide debugging information or simplify access to some internal bit of data or other:

has_parameter()

returns true if a parameter value exists for the supplied key, even if the value is zero or undef.

parameters()

returns the value of the requested parameter, if a key is supplied, or the hash of all parameters if not.

has_constraint()

returns true if a constraint value exists for the supplied key, even if the value is zero or undef.

constraints()

returns the value of the requested constraint, if a key is supplied, or the hash of all constraints if not.

sortby() sortorder() startat() step()

An AUTOLOAD method provides get and set access to the display constraints, so you can call:

  $list->startat(50);

before you display its contents, or

  $list->step();

to get the current number of items per page. Any constraints that you add to the set defined by default() will also be made available this way.

factory()

returns the local factory object, or creates one if none exists yet.

factory_class()

returns the full name of the class that should be used to instantiate the factory. Defaults to Class:DBI::Factory, of course: if you subclass the factory class, you must mention the name of the subclass here.

config()

A useful shortcut that returns the Config object attached to our factory. Added here just to keep syntax consistent.

default()

returns the value of a requested default, if a constraint name is supplied, or the hash of default values if not. Defaults can be overridden freely, but things might go awry if you don't supply at least the basic four.

Note that the keys defined here dictate what is regarded as a constraint. Anything that does not appear here will be supplied to search() as a parameter.

debug()

hands over to Class::DBI::Factory's centralised debugging message thing.

SEE ALSO

Class::DBI Class::DBI::Factory Class::DBI::Factory::Config Class::DBI::Factory::Handler Class::DBI::Pager

AUTHOR

William Ross, wross@cpan.org

COPYRIGHT

Copyright 2001-4 William Ross, spanner ltd.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.