Merge branch 'periodic-invoices-order-value-basis'
[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   );
24
25   # $::lxdebug->dump(0, "CONSPEC", \%specs);
26 }
27
28 sub read_params {
29   my ($self, %params)      = @_;
30
31   return %{ $self->form_data } if $self->form_data;
32   my $source = $self->get_models->source;
33
34   my $from_form = {
35     page            =>  $source->{ $self->form_params->[0] } || 1,
36     per_page        => ($source->{ $self->form_params->[1] } // 0) * 1,
37   };
38
39 #  my $priv              = _priv($self);
40   $params{page}         = $from_form->{page}     unless defined $params{page};
41   $params{per_page}     = $from_form->{per_page} unless defined $params{per_page};
42
43   $params{page}         = ($params{page} * 1) || 1;
44   $params{per_page}     = ($params{per_page} * 1) || $self->per_page;
45
46   $self->form_data(\%params);
47
48   %params;
49 }
50
51 sub finalize {
52   my ($self, %args)   = @_;
53
54   if ($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->calculated_params($calculated_params);
70   }
71
72   $self->paginated_args(\%args);
73
74   return %args;
75 }
76
77 sub get_current_paginate_params {
78   my ($self, %args)   = @_;
79   return () unless $self->is_enabled;
80   %{ $self->calculated_params };
81 }
82
83 #
84 # private functions
85 #
86
87 sub _callback_handler_for_paginated {
88   my ($self, %params) = @_;
89   my %form_params = $self->read_params;
90
91   if ($self->is_enabled && $form_params{page}) {
92     $params{ $self->form_params->[0] } = $form_params{page};
93     $params{ $self->form_params->[1] } = $form_params{per_page} if $form_params{per_page};
94   }
95
96   # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);
97
98   return %params;
99 }
100
101 sub init_form_params {
102   [ qw(page per_page) ]
103 }
104
105 sub init_paginate_args {
106   '__FILTER__'
107 }
108
109 1;
110 __END__
111
112 =pod
113
114 =encoding utf8
115
116 =head1 NAME
117
118 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
119 of paginating lists of database models in a controller
120
121 =head1 SYNOPSIS
122
123 In a controller:
124
125   SL::Controller::Helper::GetModels->new(
126     ..
127     paginated => {
128       form_params => [ qw(page per_page) ],
129       per_page    => 20,
130     }
131   );
132
133 In said template:
134
135   [% L.paginate_controls %]
136
137 =head1 OVERVIEW
138
139 This C<GetModels> plugin enables controllers to display a
140 paginatable list of database models with as few lines as possible.
141
142 For this to work the controller has to provide the information which
143 indexes are eligible for paginateing etc. during C<GetModels> creation.
144
145 The underlying functionality that enables the use of more than just
146 the paginate helper is provided by the controller helper
147 C<GetModels>. See the documentation for L<SL::Controller::Helper::GetModels>
148 for more information on it.
149
150 A template can use the method C<paginate_controls> from the layout
151 helper module C<L> which renders the links for navigation between the
152 pages.
153
154 This module requires that the Rose model managers use their C<Paginated>
155 helper.
156
157 =head1 OPTIONS
158
159 =over 4
160
161 =item * C<per_page>
162
163 Optional. The number of models to return per page.
164
165 Defaults to the underlying database model's default number of models
166 per page.
167
168 =item * C<form_params>
169
170 Optional. An array reference with exactly two strings that name the
171 indexes in C<$::form> in which the current page's number (the first
172 element in the array) and the number of models per page (the second
173 element in the array) are stored.
174
175 Defaults to the values C<page> and C<per_page> if missing.
176
177 =back
178
179 =head1 INSTANCE FUNCTIONS
180
181 These functions are called on a C<GetModels> instance and delegated here.
182
183 =over 4
184
185 =item C<get_paginate_spec>
186
187 Returns a hash containing the currently active paginate
188 parameters. The following keys are returned:
189
190 =over 4
191
192 =item * C<page>
193
194 The currently active page number (numbering starts at 1).
195
196 =item * C<per_page>
197
198 Number of models per page (at least 1).
199
200 =item * C<num_pages>
201
202 Number of pages to display (at least 1).
203
204 =item * C<common_pages>
205
206 An array reference with one hash reference for each possible
207 page. Each hash ref contains the keys C<active> (C<1> if that page is
208 the currently active page), C<page> (the page number this hash
209 reference describes) and C<visible> (whether or not it should be
210 displayed).
211
212 =back
213
214 =item C<get_current_paginate_params>
215
216 Returns a hash reference to the paginate spec structure given in the
217 configuration after normalization (hash reference construction,
218 applying default parameters etc).
219
220 =back
221
222 =head1 BUGS
223
224 C<common_pages> generates an array with an entry for every page, which gets
225 slow if there are a lot of entries. Current observation holds that up to about
226 1000 pages there is no noticable slowdown, but at about 10000 it gets
227 noticable. At 100k-500k pages it's takes way too long and should be remodelled.
228
229 This case currently only applies for databases with very large amounts of parts
230 that get paginated, but BackgroundJobHistory can also accumulate.
231
232 =head1 AUTHOR
233
234 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
235
236 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
237
238 =cut