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