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