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