1 package SL::Controller::Helper::Paginated;
 
   5 use Exporter qw(import);
 
   6 our @EXPORT = qw(make_paginated get_paginate_spec get_current_paginate_params _save_current_paginate_params _get_models_handler_for_paginated _callback_handler_for_paginated disable_pagination);
 
   8 use constant PRIV => '__paginatedhelper_priv';
 
  10 use List::Util qw(min);
 
  12 my %controller_paginate_spec;
 
  15   my ($class, %specs)               = @_;
 
  17   $specs{MODEL}                   ||=  $class->controller_name;
 
  18   $specs{MODEL}                     =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
 
  19   $specs{PER_PAGE}                ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page;
 
  20   $specs{FORM_PARAMS}             ||= [ qw(page per_page) ];
 
  21   $specs{PAGINATE_ARGS}           ||= '__FILTER__';
 
  23   $specs{ONLY}                      = [ $specs{ONLY} ] if !ref $specs{ONLY};
 
  24   $specs{ONLY_MAP}                  = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
 
  26   $controller_paginate_spec{$class} = \%specs;
 
  28   my %hook_params                   = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
 
  29   $class->run_before('_save_current_paginate_params', %hook_params);
 
  31   SL::Controller::Helper::GetModels::register_get_models_handlers(
 
  33     callback   => '_callback_handler_for_paginated',
 
  34     get_models => '_get_models_handler_for_paginated',
 
  38   # $::lxdebug->dump(0, "CONSPEC", \%specs);
 
  41 sub get_paginate_spec {
 
  42   my ($class_or_self) = @_;
 
  44   return $controller_paginate_spec{ref($class_or_self) || $class_or_self};
 
  47 sub get_current_paginate_params {
 
  48   my ($self, %params)   = @_;
 
  50   my $spec              = $self->get_paginate_spec;
 
  52   my $priv              = _priv($self);
 
  53   $params{page}         = $priv->{page}     unless defined $params{page};
 
  54   $params{per_page}     = $priv->{per_page} unless defined $params{per_page};
 
  56   my %paginate_params   =  (
 
  57     page                => ($params{page}     * 1) || 1,
 
  58     per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
 
  61   # try to use Filtered if available and nothing else is configured, but don't
 
  62   # blow up if the controller does not use Filtered
 
  63   my %paginate_args     = ref($spec->{PAGINATE_ARGS}) eq 'CODE'       ? %{ $spec->{PAGINATE_ARGS}->($self) }
 
  64                         :     $spec->{PAGINATE_ARGS}  eq '__FILTER__'
 
  65                            && $self->can('get_current_filter_params') ? $self->get_current_filter_params
 
  66                         :     $spec->{PAGINATE_ARGS}  ne '__FILTER__' ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
 
  68   my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
 
  70   # $::lxdebug->dump(0, "get_current_paginate_params: ", $calculated_params);
 
  72   return %{ $calculated_params };
 
  75 sub disable_pagination {
 
  77   _priv($self)->{disabled} = 1;
 
  84 sub _save_current_paginate_params {
 
  87   return if !_is_enabled($self);
 
  89   my $paginate_spec = $self->get_paginate_spec;
 
  91     page            => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
 
  92     per_page        => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1,
 
  95   # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
 
  98 sub _callback_handler_for_paginated {
 
  99   my ($self, %params) = @_;
 
 100   my $priv            = _priv($self);
 
 102   if (_is_enabled($self) && $priv->{page}) {
 
 103     my $paginate_spec                             = $self->get_paginate_spec;
 
 104     $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page};
 
 105     $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page};
 
 108   # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);
 
 113 sub _get_models_handler_for_paginated {
 
 114   my ($self, %params)    = @_;
 
 115   my $spec               = $self->get_paginate_spec;
 
 116   $params{model}       ||= $spec->{MODEL};
 
 118   "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self);
 
 120   # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
 
 127   $self->{PRIV()} ||= {};
 
 128   return $self->{PRIV()};
 
 133   return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'});
 
 145 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
 
 146 of paginating lists of database models in a controller
 
 152   use SL::Controller::Helper::GetModels;
 
 153   use SL::Controller::Helper::Paginated;
 
 155   __PACKAGE__->make_paginated(
 
 156     MODEL       => 'BackgroundJobHistory',
 
 157     ONLY        => [ qw(list) ],
 
 158     FORM_PARAMS => [ qw(page per_page) ],
 
 164     my $paginated_models = $self->get_models;
 
 165     $self->render('controller/list', ENTRIES => $paginated_models);
 
 180     [% FOREACH entry = ENTRIES %]
 
 188   [% L.paginate_controls %]
 
 192 This specialized helper module enables controllers to display a
 
 193 paginatable list of database models with as few lines as possible. It
 
 194 can also be combined trivially with the L<SL::Controller::Sorted>
 
 195 helper for sortable lists.
 
 197 For this to work the controller has to provide the information which
 
 198 indexes are eligible for paginateing etc. by a call to
 
 199 L<make_paginated> at compile time.
 
 201 The underlying functionality that enables the use of more than just
 
 202 the paginate helper is provided by the controller helper
 
 203 C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
 
 204 more information on it.
 
 206 A template can use the method C<paginate_controls> from the layout
 
 207 helper module C<L> which renders the links for navigation between the
 
 210 This module requires that the Rose model managers use their C<Paginated>
 
 213 The C<Paginated> helper hooks into the controller call to the action via
 
 214 a C<run_before> hook. This is done so that it can remember the paginate
 
 215 parameters that were used in the current view.
 
 217 =head1 PACKAGE FUNCTIONS
 
 221 =item C<make_paginated %paginate_spec>
 
 223 This function must be called by a controller at compile time. It is
 
 224 uesd to set the various parameters required for this helper to do its
 
 227 The hash C<%paginate_spec> can include the following parameters:
 
 233 Optional. A string: the name of the Rose database model that is used
 
 234 as a default in certain cases. If this parameter is missing then it is
 
 235 derived from the controller's package (e.g. for the controller
 
 236 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
 
 237 C<BackgroundJobHistory>).
 
 239 =item * C<PAGINATE_ARGS>
 
 241 Optional. Either a code reference or the name of function to be called
 
 242 on the controller importing this helper.
 
 244 If this funciton is given then the paginate helper calls it whenever
 
 245 it has to count the total number of models for calculating the number
 
 246 of pages to display. The function must return a hash reference with
 
 247 elements suitable for passing to a Rose model manager's C<get_all>
 
 250 This can be used e.g. when filtering is used.
 
 254 Optional. An integer: the number of models to return per page.
 
 256 Defaults to the underlying database model's default number of models
 
 259 =item * C<FORM_PARAMS>
 
 261 Optional. An array reference with exactly two strings that name the
 
 262 indexes in C<$::form> in which the current page's number (the first
 
 263 element in the array) and the number of models per page (the second
 
 264 element in the array) are stored.
 
 266 Defaults to the values C<page> and C<per_page> if missing.
 
 270 Optional. An array reference containing a list of action names for
 
 271 which the paginate parameters should be saved. If missing or empty then
 
 272 all actions invoked on the controller are monitored.
 
 278 =head1 INSTANCE FUNCTIONS
 
 280 These functions are called on a controller instance.
 
 284 =item C<get_paginate_spec>
 
 286 Returns a hash containing the currently active paginate
 
 287 parameters. The following keys are returned:
 
 293 The currently active page number (numbering starts at 1).
 
 297 Number of models per page (at least 1).
 
 301 Number of pages to display (at least 1).
 
 303 =item * C<common_pages>
 
 305 An array reference with one hash reference for each possible
 
 306 page. Each hash ref contains the keys C<active> (C<1> if that page is
 
 307 the currently active page), C<page> (the page number this hash
 
 308 reference describes) and C<visible> (whether or not it should be
 
 313 =item C<get_current_paginate_params>
 
 315 Returns a hash reference to the paginate spec structure given in the call
 
 316 to L<make_paginated> after normalization (hash reference construction,
 
 317 applying default parameters etc).
 
 319 =item C<disable_pagination>
 
 321 Disable pagination for the duration of the current action. Can be used
 
 322 when using the attribute C<ONLY> to L<make_paginated> does not
 
 333 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>