Merge branch 'periodic-invoices-order-value-basis'
[kivitendo-erp.git] / SL / Controller / Helper / GetModels / Sorted.pm
1 package SL::Controller::Helper::GetModels::Sorted;
2
3 use strict;
4 use parent 'SL::Controller::Helper::GetModels::Base';
5
6 use Carp;
7 use List::MoreUtils qw(uniq);
8
9 use Rose::Object::MakeMethods::Generic (
10   scalar => [ qw(by dir specs form_data) ],
11   'scalar --get_set_init' => [ qw(form_params) ],
12 );
13
14 sub init {
15   my ($self, %specs) = @_;
16
17   $self->set_get_models(delete $specs{get_models});
18   my %model_sort_spec   = $self->get_models->manager->_sort_spec;
19
20   if (my $default = delete $specs{_default}) {
21     $self->by ($default->{by});
22     $self->dir($default->{dir});
23   } else {
24     $self->by ($model_sort_spec{default}[0]);
25     $self->dir($model_sort_spec{default}[1]);
26   }
27
28   while (my ($column, $spec) = each %specs) {
29     next if $column =~ m/^[A-Z_]+$/;
30
31     $spec = $specs{$column} = { title => $spec } if (ref($spec) || '') ne 'HASH';
32
33     $spec->{model}        ||= $self->get_models->model;
34     $spec->{model_column} ||= $column;
35   }
36   $self->specs(\%specs);
37
38   $self->get_models->register_handlers(
39     callback   => sub { shift; $self->_callback_handler_for_sorted(@_) },
40   );
41
42 #   $::lxdebug->dump(0, "CONSPEC", \%specs);
43 }
44
45 sub read_params {
46   my ($self, %params) = @_;
47
48   return %{ $self->form_data } if $self->form_data;
49
50   my %sort_params;
51   my ($by, $dir) = @{ $self->form_params };
52   my $source = $self->get_models->source;
53
54   if ($source->{ $by }) {
55     %sort_params = (
56       sort_by  => $source->{$by},
57       sort_dir => defined($source->{$dir}) ? $source->{$dir} * 1 : undef,
58     );
59   } elsif (!$self->by) {
60     %sort_params = %params;
61   } else {
62     %sort_params = (
63       sort_by  => $self->by,
64       sort_dir => $self->dir,
65     );
66   }
67
68   $self->form_data(\%sort_params);
69
70   return %sort_params;
71 }
72
73 sub finalize {
74   my ($self, %params) = @_;
75
76   my %sort_params     = $self->read_params;
77   my $sort_spec       = $self->specs->{ $sort_params{sort_by} };
78
79   if (!$sort_spec) {
80     no warnings 'once';
81     $::lxdebug->show_backtrace(1);
82     die "Unknown sort spec '$sort_params{sort_by}'";
83   }
84
85   $params{sort_by}    = "SL::DB::Manager::$sort_spec->{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{sort_dir});
86
87   %params;
88 }
89
90 sub set_report_generator_sort_options {
91   my ($self, %params) = @_;
92
93   $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns);
94
95   my %current_sort_params = $self->read_params;
96
97   foreach my $col (@{ $params{sortable_columns} }) {
98     $params{report}->{columns}->{$col}->{link} = $self->get_models->get_callback(
99       sort_by  => $col,
100       sort_dir => ($current_sort_params{sort_by} eq $col ? 1 - $current_sort_params{sort_dir} : $current_sort_params{sort_dir}),
101     );
102   }
103
104   $params{report}->set_sort_indicator($current_sort_params{sort_by}, 1 - $current_sort_params{sort_dir});
105
106   if ($params{report}->{export}) {
107     $params{report}->{export}->{variable_list} = [ uniq(
108       @{ $params{report}->{export}->{variable_list} },
109       @{ $self->form_params }
110     )];
111   }
112 }
113
114 #
115 # private functions
116 #
117
118 sub _callback_handler_for_sorted {
119   my ($self, %params) = @_;
120   my %spec = $self->read_params;
121
122   if ($spec{sort_by}) {
123     $params{ $self->form_params->[0] } = $spec{sort_by};
124     $params{ $self->form_params->[1] } = $spec{sort_dir};
125   }
126
127   # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
128
129   return %params;
130 }
131
132 sub init_form_params {
133   [ qw(sort_by sort_dir) ]
134 }
135
136 1;
137 __END__
138
139 =pod
140
141 =encoding utf8
142
143 =head1 NAME
144
145 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
146 of sorting lists of database models in a controller
147
148 =head1 SYNOPSIS
149
150 In a controller:
151
152   SL::Controller::Helper::GetModels->new(
153     ...
154     sorted => {
155       _default => {
156         by  => 'run_at',
157         dir => 1,
158       },
159       error        => $::locale->text('Error'),
160       package_name => $::locale->text('Package name'),
161       run_at       => $::locale->text('Run at'),
162     },
163   );
164
165 In template:
166
167   [% USE L %]
168
169   <table>
170    <tr>
171     <th>[% L.sortable_table_header('package_name') %]</th>
172     <th>[% L.sortable_table_header('run_at') %]</th>
173     <th>[% L.sortable_table_header('error') %]</th>
174    </tr>
175
176    [% FOREACH entry = ENTRIES %]
177     <tr>
178      <td>[% HTML.escape(entry.package_name) %]</td>
179      <td>[% HTML.escape(entry.run_at) %]</td>
180      <td>[% HTML.escape(entry.error) %]</td>
181     </tr>
182    [% END %]
183   </table>
184
185 =head1 OVERVIEW
186
187 This C<GetModels> plugin enables controllers to display a
188 sortable list of database models with as few lines as possible.
189
190 For this to work the controller has to provide the information which
191 indexes are eligible for sorting etc. through it's configuration of
192 C<GetModels>.
193
194 A template can then use the method C<sortable_table_header> from the layout
195 helper module C<L>.
196
197 This module requires that the Rose model managers use their
198 C<SL::DB::Helper::Sorted> helper.
199
200 =head1 OPTIONS
201
202 =over 4
203
204 =item * C<_default HASHREF>
205
206 Optional. If it exists, it is expected to contain the keys C<by> and C<dir> and
207 will be used to set the default sorting if nothing is found in C<source>.
208
209 Defaults to the underlying database model's default.
210
211 =item * C<form_params>
212
213 Optional. An array reference with exactly two strings that name the
214 indexes in C<source> in which the sort index (the first element in
215 the array) and sort direction (the second element in the array) are
216 stored.
217
218 Defaults to the values C<sort_by> and C<sort_dir> if missing.
219
220 =back
221
222 All other keys can be used for sorting. Each value to such a key can be either
223 a string or a hash reference containing certain elements. If the value is only
224 a string then such a hash reference is constructed, and the string is
225 used as the value for the C<title> key.
226
227 These possible elements are:
228
229 =over 4
230
231 =item * C<title>
232
233 Required. A user-displayable title to be used by functions like the
234 layout helper's C<sortable_table_header>. Does not have a default
235 value.
236
237 Note that this string must be the untranslated English version of the
238 string. The titles will be translated whenever they're requested.
239
240 =item * C<model>
241
242 Optional. The name of a Rose database model this sort index refers
243 to. If missing then the value of C<$sort_spec{MODEL}> is used.
244
245 =item * C<model_column>
246
247 Optional. The name of the Rose database model column this sort index
248 refers to. It must be one of the columns named by the model's
249 C<Sorted> helper (not to be confused with the controller's C<Sorted>
250 helper!).
251
252 If missing it defaults to the key in C<%sort_spec> for which this hash
253 reference is the value.
254
255 =back
256
257 =head1 INSTANCE FUNCTIONS
258
259 These functions are called on a C<GetModels> instance and delegating to this plugin.
260
261 =over 4
262
263 =item C<get_sort_spec>
264
265 Returns a hash containing the currently active sort parameters.
266
267 The key C<by> contains the active sort index referring to the
268 C<%sort_spec> given by the configuration.
269
270 The key C<dir> is either C<1> or C<0>.
271
272 =item C<get_current_sort_params>
273
274 Returns a hash reference to the sort spec structure given in the configuration
275 after normalization (hash reference construction, applying default parameters
276 etc).
277
278 =item C<set_report_generator_sort_options %params>
279
280 This function does three things with an instance of
281 L<SL::ReportGenerator>:
282
283 =over 4
284
285 =item 1. it sets the sort indicator,
286
287 =item 2. it sets the the links for those column headers that are
288 sortable and
289
290 =item 3. it adds the C<form_params> fields to the list of variables in
291 the report generator's export options.
292
293 =back
294
295 The report generator instance must be passed as the parameter
296 C<report>. The parameter C<sortable_columns> must be an array
297 reference of column names that are sortable.
298
299 The report generator instance must already have its columns and export
300 options set via calls to its L<SL::ReportGenerator::set_columns> and
301 L<SL::ReportGenerator::set_export_options> functions.
302
303 =back
304
305 =head1 BUGS
306
307 Nothing here yet.
308
309 =head1 AUTHOR
310
311 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
312
313 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
314
315 =cut