86b248c20155fc43283a2f247113859240f09e6c
[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  ne '__FILTER__' ? do { my $sub = $self->paginate_args; %{ $self->get_models->controller->$sub() } }
61                           :                                               ();
62
63     %args = $self->merge_args(\%args, \%paginate_args);
64
65     my $calculated_params = $self->get_models->manager->paginate(%paginate_params, args => \%args);
66
67     $self->calculated_params($calculated_params);
68   }
69
70   $self->paginated_args(\%args);
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 init_form_params {
100   [ qw(page per_page) ]
101 }
102
103 sub init_paginate_args {
104   '__FILTER__'
105 }
106
107 1;
108 __END__
109
110 =pod
111
112 =encoding utf8
113
114 =head1 NAME
115
116 SL::Controller::Helper::Paginated - A helper for semi-automatic handling
117 of paginating lists of database models in a controller
118
119 =head1 SYNOPSIS
120
121 In a controller:
122
123   SL::Controller::Helper::GetModels->new(
124     ..
125     paginated => {
126       form_params => [ qw(page per_page) ],
127       per_page    => 20,
128     }
129   );
130
131 In said template:
132
133   [% L.paginate_controls %]
134
135 =head1 OVERVIEW
136
137 This C<GetModels> plugin enables controllers to display a
138 paginatable list of database models with as few lines as possible.
139
140 For this to work the controller has to provide the information which
141 indexes are eligible for paginateing etc. during C<GetModels> creation.
142
143 The underlying functionality that enables the use of more than just
144 the paginate helper is provided by the controller helper
145 C<GetModels>. See the documentation for L<SL::Controller::Helper::GetModels>
146 for more information on it.
147
148 A template can use the method C<paginate_controls> from the layout
149 helper module C<L> which renders the links for navigation between the
150 pages.
151
152 This module requires that the Rose model managers use their C<Paginated>
153 helper.
154
155 =head1 OPTIONS
156
157 =over 4
158
159 =item * C<per_page>
160
161 Optional. The number of models to return per page.
162
163 Defaults to the underlying database model's default number of models
164 per page.
165
166 =item * C<form_params>
167
168 Optional. An array reference with exactly two strings that name the
169 indexes in C<$::form> in which the current page's number (the first
170 element in the array) and the number of models per page (the second
171 element in the array) are stored.
172
173 Defaults to the values C<page> and C<per_page> if missing.
174
175 =back
176
177 =head1 INSTANCE FUNCTIONS
178
179 These functions are called on a C<GetModels> instance and delegated here.
180
181 =over 4
182
183 =item C<get_paginate_spec>
184
185 Returns a hash containing the currently active paginate
186 parameters. The following keys are returned:
187
188 =over 4
189
190 =item * C<page>
191
192 The currently active page number (numbering starts at 1).
193
194 =item * C<per_page>
195
196 Number of models per page (at least 1).
197
198 =item * C<num_pages>
199
200 Number of pages to display (at least 1).
201
202 =item * C<common_pages>
203
204 An array reference with one hash reference for each possible
205 page. Each hash ref contains the keys C<active> (C<1> if that page is
206 the currently active page), C<page> (the page number this hash
207 reference describes) and C<visible> (whether or not it should be
208 displayed).
209
210 =back
211
212 =item C<get_current_paginate_params>
213
214 Returns a hash reference to the paginate spec structure given in the
215 configuration after normalization (hash reference construction,
216 applying default parameters etc).
217
218 =back
219
220 =head1 BUGS
221
222 C<common_pages> generates an array with an entry for every page, which gets
223 slow if there are a lot of entries. Current observation holds that up to about
224 1000 pages there is no noticable slowdown, but at about 10000 it gets
225 noticable. At 100k-500k pages it's takes way too long and should be remodelled.
226
227 This case currently only applies for databases with very large amounts of parts
228 that get paginated, but BackgroundJobHistory can also accumulate.
229
230 =head1 AUTHOR
231
232 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
233
234 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
235
236 =cut