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