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