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