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