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) ];
22 $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY};
23 $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
25 $controller_paginate_spec{$class} = \%specs;
27 my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
28 $class->run_before('_save_current_paginate_params', %hook_params);
30 SL::Controller::Helper::GetModels::register_get_models_handlers(
32 callback => '_callback_handler_for_paginated',
33 get_models => '_get_models_handler_for_paginated',
37 # $::lxdebug->dump(0, "CONSPEC", \%specs);
40 sub get_paginate_spec {
41 my ($class_or_self) = @_;
43 return $controller_paginate_spec{ref($class_or_self) || $class_or_self};
46 sub get_current_paginate_params {
47 my ($self, %params) = @_;
49 my $spec = $self->get_paginate_spec;
51 my $priv = _priv($self);
52 $params{page} = $priv->{page} unless defined $params{page};
53 $params{per_page} = $priv->{per_page} unless defined $params{per_page};
55 my %paginate_params = (
56 page => ($params{page} * 1) || 1,
57 per_page => ($params{per_page} * 1) || $spec->{PER_PAGE},
60 my %paginate_args = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) }
61 : $spec->{PAGINATE_ARGS} ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
63 my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
65 page => min($paginate_params{page}, $calculated_params->{max}),
66 per_page => $paginate_params{per_page},
67 num_pages => $calculated_params->{max},
68 common_pages => $calculated_params->{common},
71 # $::lxdebug->dump(0, "get_current_paginate_params: ", \%paginate_params);
73 return %paginate_params;
76 sub disable_pagination {
78 _priv($self)->{disabled} = 1;
85 sub _save_current_paginate_params {
88 return if !_is_enabled($self);
90 my $paginate_spec = $self->get_paginate_spec;
92 page => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
93 per_page => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1,
96 # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
99 sub _callback_handler_for_paginated {
100 my ($self, %params) = @_;
101 my $priv = _priv($self);
103 if (_is_enabled($self) && $priv->{page}) {
104 my $paginate_spec = $self->get_paginate_spec;
105 $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page};
106 $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page};
109 # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);
114 sub _get_models_handler_for_paginated {
115 my ($self, %params) = @_;
116 my $spec = $self->get_paginate_spec;
117 $params{model} ||= $spec->{MODEL};
119 "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self);
121 # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
128 $self->{PRIV()} ||= {};
129 return $self->{PRIV()};
134 return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'});
146 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
147 of paginating lists of database models in a controller
153 use SL::Controller::Helper::GetModels;
154 use SL::Controller::Helper::Paginated;
156 __PACKAGE__->make_paginated(
157 MODEL => 'BackgroundJobHistory',
158 ONLY => [ qw(list) ],
159 FORM_PARAMS => [ qw(page per_page) ],
165 my $paginated_models = $self->get_models;
166 $self->render('controller/list', ENTRIES => $paginated_models);
181 [% FOREACH entry = ENTRIES %]
189 [% L.paginate_controls %]
193 This specialized helper module enables controllers to display a
194 paginatable list of database models with as few lines as possible. It
195 can also be combined trivially with the L<SL::Controller::Sorted>
196 helper for sortable lists.
198 For this to work the controller has to provide the information which
199 indexes are eligible for paginateing etc. by a call to
200 L<make_paginated> at compile time.
202 The underlying functionality that enables the use of more than just
203 the paginate helper is provided by the controller helper
204 C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
205 more information on it.
207 A template can use the method C<paginate_controls> from the layout
208 helper module C<L> which renders the links for navigation between the
211 This module requires that the Rose model managers use their C<Paginated>
214 The C<Paginated> helper hooks into the controller call to the action via
215 a C<run_before> hook. This is done so that it can remember the paginate
216 parameters that were used in the current view.
218 =head1 PACKAGE FUNCTIONS
222 =item C<make_paginated %paginate_spec>
224 This function must be called by a controller at compile time. It is
225 uesd to set the various parameters required for this helper to do its
228 The hash C<%paginate_spec> can include the following parameters:
234 Optional. A string: the name of the Rose database model that is used
235 as a default in certain cases. If this parameter is missing then it is
236 derived from the controller's package (e.g. for the controller
237 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
238 C<BackgroundJobHistory>).
240 =item * C<PAGINATE_ARGS>
242 Optional. Either a code reference or the name of function to be called
243 on the controller importing this helper.
245 If this funciton is given then the paginate helper calls it whenever
246 it has to count the total number of models for calculating the number
247 of pages to display. The function must return a hash reference with
248 elements suitable for passing to a Rose model manager's C<get_all>
251 This can be used e.g. when filtering is used.
255 Optional. An integer: the number of models to return per page.
257 Defaults to the underlying database model's default number of models
260 =item * C<FORM_PARAMS>
262 Optional. An array reference with exactly two strings that name the
263 indexes in C<$::form> in which the current page's number (the first
264 element in the array) and the number of models per page (the second
265 element in the array) are stored.
267 Defaults to the values C<page> and C<per_page> if missing.
271 Optional. An array reference containing a list of action names for
272 which the paginate parameters should be saved. If missing or empty then
273 all actions invoked on the controller are monitored.
279 =head1 INSTANCE FUNCTIONS
281 These functions are called on a controller instance.
285 =item C<get_paginate_spec>
287 Returns a hash containing the currently active paginate
288 parameters. The following keys are returned:
294 The currently active page number (numbering starts at 1).
298 Number of models per page (at least 1).
302 Number of pages to display (at least 1).
304 =item * C<common_pages>
306 An array reference with one hash reference for each possible
307 page. Each hash ref contains the keys C<active> (C<1> if that page is
308 the currently active page), C<page> (the page number this hash
309 reference describes) and C<visible> (whether or not it should be
314 =item C<get_current_paginate_params>
316 Returns a hash reference to the paginate spec structure given in the call
317 to L<make_paginated> after normalization (hash reference construction,
318 applying default parameters etc).
320 =item C<disable_pagination>
322 Disable pagination for the duration of the current action. Can be used
323 when using the attribute C<ONLY> to L<make_paginated> does not
334 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>