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