Merge remote branch 'origin/master'
[kivitendo-erp.git] / SL / Controller / Helper / Paginated.pm
1 package SL::Controller::Helper::Paginated;
2
3 use strict;
4
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);
7
8 use constant PRIV => '__paginatedhelper_priv';
9
10 use List::Util qw(min);
11
12 my %controller_paginate_spec;
13
14 sub make_paginated {
15   my ($class, %specs)       = @_;
16
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{ONLY}            ||= [];
22   $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
23   $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
24
25   $controller_paginate_spec{$class} = \%specs;
26
27   my %hook_params           = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
28   $class->run_before('_save_current_paginate_params', %hook_params);
29
30   SL::Controller::Helper::GetModels::register_get_models_handlers(
31     $class,
32     callback   => '_callback_handler_for_paginated',
33     get_models => '_get_models_handler_for_paginated',
34     ONLY       => $specs{ONLY},
35   );
36
37   # $::lxdebug->dump(0, "CONSPEC", \%specs);
38 }
39
40 sub get_paginate_spec {
41   my ($class_or_self) = @_;
42
43   return $controller_paginate_spec{ref($class_or_self) || $class_or_self};
44 }
45
46 sub get_current_paginate_params {
47   my ($self, %params)   = @_;
48
49   my $spec              = $self->get_paginate_spec;
50
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};
54
55   my %paginate_params   =  (
56     page                => ($params{page}     * 1) || 1,
57     per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
58   );
59
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() } }
62                         :                                         ();
63   my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
64   %paginate_params      = (
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},
69   );
70
71   # $::lxdebug->dump(0, "get_current_paginate_params: ", \%paginate_params);
72
73   return %paginate_params;
74 }
75
76 sub disable_pagination {
77   my ($self)               = @_;
78   _priv($self)->{disabled} = 1;
79 }
80
81 #
82 # private functions
83 #
84
85 sub _save_current_paginate_params {
86   my ($self)        = @_;
87
88   return if !_is_enabled($self);
89
90   my $paginate_spec = $self->get_paginate_spec;
91   $self->{PRIV()}   = {
92     page            => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
93     per_page        => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1,
94   };
95
96   # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
97 }
98
99 sub _callback_handler_for_paginated {
100   my ($self, %params) = @_;
101   my $priv            = _priv($self);
102
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};
107   }
108
109   # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);
110
111   return %params;
112 }
113
114 sub _get_models_handler_for_paginated {
115   my ($self, %params)    = @_;
116   my $spec               = $self->get_paginate_spec;
117   $params{model}       ||= $spec->{MODEL};
118
119   "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self);
120
121   # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
122
123   return %params;
124 }
125
126 sub _priv {
127   my ($self)        = @_;
128   $self->{PRIV()} ||= {};
129   return $self->{PRIV()};
130 }
131
132 sub _is_enabled {
133   my ($self) = @_;
134   return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'});
135 }
136
137 1;
138 __END__
139
140 =pod
141
142 =encoding utf8
143
144 =head1 NAME
145
146 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
147 of paginating lists of database models in a controller
148
149 =head1 SYNOPSIS
150
151 In a controller:
152
153   use SL::Controller::Helper::GetModels;
154   use SL::Controller::Helper::Paginated;
155
156   __PACKAGE__->make_paginated(
157     MODEL       => 'BackgroundJobHistory',
158     ONLY        => [ qw(list) ],
159     FORM_PARAMS => [ qw(page per_page) ],
160   );
161
162   sub action_list {
163     my ($self) = @_;
164
165     my $paginated_models = $self->get_models;
166     $self->render('controller/list', ENTRIES => $paginated_models);
167   }
168
169 In said template:
170
171   [% USE L %]
172
173   <table>
174    <thead>
175     <tr>
176      ...
177     </tr>
178    </thead>
179
180    <tbody>
181     [% FOREACH entry = ENTRIES %]
182      <tr>
183       ...
184      </tr>
185     [% END %]
186    </tbody>
187   </table>
188
189   [% L.paginate_controls %]
190
191 =head1 OVERVIEW
192
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.
197
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.
201
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.
206
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
209 pages.
210
211 This module requires that the Rose model managers use their C<Paginated>
212 helper.
213
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.
217
218 =head1 PACKAGE FUNCTIONS
219
220 =over 4
221
222 =item C<make_paginated %paginate_spec>
223
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
226 magic.
227
228 The hash C<%paginate_spec> can include the following parameters:
229
230 =over 4
231
232 =item * C<MODEL>
233
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>).
239
240 =item * C<PAGINATE_ARGS>
241
242 Optional. Either a code reference or the name of function to be called
243 on the controller importing this helper.
244
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>
249 function.
250
251 This can be used e.g. when filtering is used.
252
253 =item * C<PER_PAGE>
254
255 Optional. An integer: the number of models to return per page.
256
257 Defaults to the underlying database model's default number of models
258 per page.
259
260 =item * C<FORM_PARAMS>
261
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.
266
267 Defaults to the values C<page> and C<per_page> if missing.
268
269 =item * C<ONLY>
270
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.
274
275 =back
276
277 =back
278
279 =head1 INSTANCE FUNCTIONS
280
281 These functions are called on a controller instance.
282
283 =over 4
284
285 =item C<get_paginate_spec>
286
287 Returns a hash containing the currently active paginate
288 parameters. The following keys are returned:
289
290 =over 4
291
292 =item * C<page>
293
294 The currently active page number (numbering starts at 1).
295
296 =item * C<per_page>
297
298 Number of models per page (at least 1).
299
300 =item * C<num_pages>
301
302 Number of pages to display (at least 1).
303
304 =item * C<common_pages>
305
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
310 displayed).
311
312 =back
313
314 =item C<get_current_paginate_params>
315
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).
319
320 =item C<disable_pagination>
321
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
324 cover all cases.
325
326 =back
327
328 =head1 BUGS
329
330 Nothing here yet.
331
332 =head1 AUTHOR
333
334 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
335
336 =cut