Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
[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 my $controller_paginate_spec;
11
12 sub make_paginated {
13   my ($class, %specs)       = @_;
14
15   $specs{MODEL}           ||=  $class->controller_name;
16   $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
17   $specs{PER_PAGE}        ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page;
18   $specs{FORM_PARAMS}     ||= [ qw(page per_page) ];
19   $specs{ONLY}            ||= [];
20   $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
21   $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
22
23   $controller_paginate_spec = \%specs;
24
25   my %hook_params           = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
26   $class->run_before('_save_current_paginate_params', %hook_params);
27
28   SL::Controller::Helper::GetModels::register_get_models_handlers(
29     $class,
30     callback   => '_callback_handler_for_paginated',
31     get_models => '_get_models_handler_for_paginated',
32     ONLY       => $specs{ONLY},
33   );
34
35   # $::lxdebug->dump(0, "CONSPEC", \%specs);
36 }
37
38 sub get_paginate_spec {
39   my ($class_or_self) = @_;
40
41   return $controller_paginate_spec;
42 }
43
44 sub get_current_paginate_params {
45   my ($self, %params)   = @_;
46
47   my $spec              = $self->get_paginate_spec;
48
49   my $priv              = _priv($self);
50   $params{page}         = $priv->{page}     unless defined $params{page};
51   $params{per_page}     = $priv->{per_page} unless defined $params{per_page};
52
53   my %paginate_params   =  (
54     page                => ($params{page}     * 1) || 1,
55     per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
56   );
57
58   my %paginate_args     = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) }
59                         :     $spec->{PAGINATE_ARGS}            ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
60                         :                                         ();
61   my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
62   %paginate_params      = (
63     %paginate_params,
64     num_pages    => $calculated_params->{max},
65     common_pages => $calculated_params->{common},
66   );
67
68   # $::lxdebug->dump(0, "get_current_paginate_params: ", \%paginate_params);
69
70   return %paginate_params;
71 }
72
73 sub disable_pagination {
74   my ($self)               = @_;
75   _priv($self)->{disabled} = 1;
76 }
77
78 #
79 # private functions
80 #
81
82 sub _save_current_paginate_params {
83   my ($self)        = @_;
84
85   return if !_is_enabled($self);
86
87   my $paginate_spec = $self->get_paginate_spec;
88   $self->{PRIV()}   = {
89     page            => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
90     per_page        => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1,
91   };
92
93   # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
94 }
95
96 sub _callback_handler_for_paginated {
97   my ($self, %params) = @_;
98   my $priv            = _priv($self);
99
100   if (_is_enabled($self) && $priv->{page}) {
101     my $paginate_spec                             = $self->get_paginate_spec;
102     $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page};
103     $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page};
104   }
105
106   # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);
107
108   return %params;
109 }
110
111 sub _get_models_handler_for_paginated {
112   my ($self, %params)    = @_;
113   my $spec               = $self->get_paginate_spec;
114   $params{model}       ||= $spec->{MODEL};
115
116   "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self);
117
118   # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
119
120   return %params;
121 }
122
123 sub _priv {
124   my ($self)        = @_;
125   $self->{PRIV()} ||= {};
126   return $self->{PRIV()};
127 }
128
129 sub _is_enabled {
130   my ($self) = @_;
131   return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'});
132 }
133
134 1;
135 __END__
136
137 =pod
138
139 =encoding utf8
140
141 =head1 NAME
142
143 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
144 of paginating lists of database models in a controller
145
146 =head1 SYNOPSIS
147
148 In a controller:
149
150   use SL::Controller::Helper::GetModels;
151   use SL::Controller::Helper::Paginated;
152
153   __PACKAGE__->make_paginated(
154     MODEL       => 'BackgroundJobHistory',
155     ONLY        => [ qw(list) ],
156     FORM_PARAMS => [ qw(page per_page) ],
157   );
158
159   sub action_list {
160     my ($self) = @_;
161
162     my $paginated_models = $self->get_models;
163     $self->render('controller/list', ENTRIES => $paginated_models);
164   }
165
166 In said template:
167
168   [% USE L %]
169
170   <table>
171    <thead>
172     <tr>
173      ...
174     </tr>
175    </thead>
176
177    <tbody>
178     [% FOREACH entry = ENTRIES %]
179      <tr>
180       ...
181      </tr>
182     [% END %]
183    </tbody>
184   </table>
185
186   [% L.paginate_controls %]
187
188 =head1 OVERVIEW
189
190 This specialized helper module enables controllers to display a
191 paginatable list of database models with as few lines as possible. It
192 can also be combined trivially with the L<SL::Controller::Sorted>
193 helper for sortable lists.
194
195 For this to work the controller has to provide the information which
196 indexes are eligible for paginateing etc. by a call to
197 L<make_paginated> at compile time.
198
199 The underlying functionality that enables the use of more than just
200 the paginate helper is provided by the controller helper
201 C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
202 more information on it.
203
204 A template can use the method C<paginate_controls> from the layout
205 helper module C<L> which renders the links for navigation between the
206 pages.
207
208 This module requires that the Rose model managers use their C<Paginated>
209 helper.
210
211 The C<Paginated> helper hooks into the controller call to the action via
212 a C<run_before> hook. This is done so that it can remember the paginate
213 parameters that were used in the current view.
214
215 =head1 PACKAGE FUNCTIONS
216
217 =over 4
218
219 =item C<make_paginated %paginate_spec>
220
221 This function must be called by a controller at compile time. It is
222 uesd to set the various parameters required for this helper to do its
223 magic.
224
225 The hash C<%paginate_spec> can include the following parameters:
226
227 =over 4
228
229 =item * C<MODEL>
230
231 Optional. A string: the name of the Rose database model that is used
232 as a default in certain cases. If this parameter is missing then it is
233 derived from the controller's package (e.g. for the controller
234 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
235 C<BackgroundJobHistory>).
236
237 =item * C<PAGINATE_ARGS>
238
239 Optional. Either a code reference or the name of function to be called
240 on the controller importing this helper.
241
242 If this funciton is given then the paginate helper calls it whenever
243 it has to count the total number of models for calculating the number
244 of pages to display. The function must return a hash reference with
245 elements suitable for passing to a Rose model manager's C<get_all>
246 function.
247
248 This can be used e.g. when filtering is used.
249
250 =item * C<PER_PAGE>
251
252 Optional. An integer: the number of models to return per page.
253
254 Defaults to the underlying database model's default number of models
255 per page.
256
257 =item * C<FORM_PARAMS>
258
259 Optional. An array reference with exactly two strings that name the
260 indexes in C<$::form> in which the current page's number (the first
261 element in the array) and the number of models per page (the second
262 element in the array) are stored.
263
264 Defaults to the values C<page> and C<per_page> if missing.
265
266 =item * C<ONLY>
267
268 Optional. An array reference containing a list of action names for
269 which the paginate parameters should be saved. If missing or empty then
270 all actions invoked on the controller are monitored.
271
272 =back
273
274 =back
275
276 =head1 INSTANCE FUNCTIONS
277
278 These functions are called on a controller instance.
279
280 =over 4
281
282 =item C<get_paginate_spec>
283
284 Returns a hash containing the currently active paginate
285 parameters. The following keys are returned:
286
287 =over 4
288
289 =item * C<page>
290
291 The currently active page number (numbering starts at 1).
292
293 =item * C<per_page>
294
295 Number of models per page (at least 1).
296
297 =item * C<num_pages>
298
299 Number of pages to display (at least 1).
300
301 =item * C<common_pages>
302
303 An array reference with one hash reference for each possible
304 page. Each hash ref contains the keys C<active> (C<1> if that page is
305 the currently active page), C<page> (the page number this hash
306 reference describes) and C<visible> (whether or not it should be
307 displayed).
308
309 =back
310
311 =item C<get_current_paginate_params>
312
313 Returns a hash reference to the paginate spec structure given in the call
314 to L<make_paginated> after normalization (hash reference construction,
315 applying default parameters etc).
316
317 =item C<disable_pagination>
318
319 Disable pagination for the duration of the current action. Can be used
320 when using the attribute C<ONLY> to L<make_paginated> does not
321 cover all cases.
322
323 =back
324
325 =head1 BUGS
326
327 Nothing here yet.
328
329 =head1 AUTHOR
330
331 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
332
333 =cut