9e133b8f6a0a4422bee79510a939e554e96fc6fb
[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) ],
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     get_models => sub { shift; $self->_get_models_handler_for_sorted(@_) },
41   );
42
43 #   $::lxdebug->dump(0, "CONSPEC", \%specs);
44 }
45
46 sub get_current_sort_params {
47   my ($self, %params) = @_;
48
49   my %sort_params;
50   my ($by, $dir) = @{ $self->form_params };
51
52   if ($::form->{ $by }) {
53     %sort_params = (
54       sort_by  => $::form->{$by},
55       sort_dir => defined($::form->{$dir}) ? $::form->{$dir} * 1 : undef,
56     );
57   } elsif (!$self->by) {
58     %sort_params = %params;
59   } else {
60     %sort_params = (
61       sort_by  => $self->by,
62       sort_dir => $self->dir,
63     );
64   }
65
66   return %sort_params;
67 }
68
69 sub set_report_generator_sort_options {
70   my ($self, %params) = @_;
71
72   $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns);
73
74   my %current_sort_params = $self->get_current_sort_params;
75
76   foreach my $col (@{ $params{sortable_columns} }) {
77     $params{report}->{columns}->{$col}->{link} = $self->get_models->get_callback(
78       sort_by  => $col,
79       sort_dir => ($current_sort_params{sort_by} eq $col ? 1 - $current_sort_params{sort_dir} : $current_sort_params{sort_dir}),
80     );
81   }
82
83   $params{report}->set_sort_indicator($current_sort_params{sort_by}, 1 - $current_sort_params{sort_dir});
84
85   if ($params{report}->{export}) {
86     $params{report}->{export}->{variable_list} = [ uniq(
87       @{ $params{report}->{export}->{variable_list} },
88       @{ $self->form_params }
89     )];
90   }
91 }
92
93 #
94 # private functions
95 #
96
97 sub _callback_handler_for_sorted {
98   my ($self, %params) = @_;
99   my %spec = $self->get_current_sort_params;
100
101   if ($spec{sort_by}) {
102     $params{ $self->form_params->[0] } = $spec{sort_by};
103     $params{ $self->form_params->[1] } = $spec{sort_dir};
104   }
105
106   # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
107
108   return %params;
109 }
110
111 sub _get_models_handler_for_sorted {
112   my ($self, %params) = @_;
113
114   my %sort_params     = $self->get_current_sort_params;
115   my $sort_spec       = $self->specs->{ $sort_params{sort_by} };
116
117   $params{sort_by}    = "SL::DB::Manager::$sort_spec->{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{sort_dir});
118
119   # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
120
121   return %params;
122 }
123
124
125 sub init_form_params {
126   [ qw(sort_by sort_dir) ]
127 }
128
129 1;
130 __END__
131
132 =pod
133
134 =encoding utf8
135
136 =head1 NAME
137
138 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
139 of sorting lists of database models in a controller
140
141 =head1 SYNOPSIS
142
143 In a controller:
144
145   use SL::Controller::Helper::GetModels;
146   use SL::Controller::Helper::Sorted;
147
148   __PACKAGE__->make_sorted(
149     DEFAULT_BY   => 'run_at',
150     DEFAULT_DIR  => 1,
151     MODEL        => 'BackgroundJobHistory',
152     ONLY         => [ qw(list) ],
153
154     error        => $::locale->text('Error'),
155     package_name => $::locale->text('Package name'),
156     run_at       => $::locale->text('Run at'),
157   );
158
159   sub action_list {
160     my ($self) = @_;
161
162     my $sorted_models = $self->get_models;
163     $self->render('controller/list', ENTRIES => $sorted_models);
164   }
165
166 In said template:
167
168   [% USE L %]
169
170   <table>
171    <tr>
172     <th>[% L.sortable_table_header('package_name') %]</th>
173     <th>[% L.sortable_table_header('run_at') %]</th>
174     <th>[% L.sortable_table_header('error') %]</th>
175    </tr>
176
177    [% FOREACH entry = ENTRIES %]
178     <tr>
179      <td>[% HTML.escape(entry.package_name) %]</td>
180      <td>[% HTML.escape(entry.run_at) %]</td>
181      <td>[% HTML.escape(entry.error) %]</td>
182     </tr>
183    [% END %]
184   </table>
185
186 =head1 OVERVIEW
187
188 This specialized helper module enables controllers to display a
189 sortable list of database models with as few lines as possible.
190
191 For this to work the controller has to provide the information which
192 indexes are eligible for sorting etc. by a call to L<make_sorted> at
193 compile time.
194
195 The underlying functionality that enables the use of more than just
196 the sort helper is provided by the controller helper C<GetModels>. It
197 provides mechanisms for helpers like this one to hook into certain
198 calls made by the controller (C<get_callback> and C<get_models>) so
199 that the specialized helpers can inject their parameters into the
200 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
201
202 A template on the other hand can use the method
203 C<sortable_table_header> from the layout helper module C<L>.
204
205 This module requires that the Rose model managers use their C<Sorted>
206 helper.
207
208 The C<Sorted> helper hooks into the controller call to the action via
209 a C<run_before> hook. This is done so that it can remember the sort
210 parameters that were used in the current view.
211
212 =head1 PACKAGE FUNCTIONS
213
214 =over 4
215
216 =item C<make_sorted %sort_spec>
217
218 This function must be called by a controller at compile time. It is
219 uesd to set the various parameters required for this helper to do its
220 magic.
221
222 There are two sorts of keys in the hash C<%sort_spec>. The first kind
223 is written in all upper-case. Those parameters are control
224 parameters. The second kind are all lower-case and represent indexes
225 that can be used for sorting (similar to database column names). The
226 second kind are also the indexes you use in a template when calling
227 C<[% L.sorted_table_header(...) %]>.
228
229 Control parameters include the following:
230
231 =over 4
232
233 =item * C<MODEL>
234
235 Optional. A string: the name of the Rose database model that is used
236 as a default in certain cases. If this parameter is missing then it is
237 derived from the controller's package (e.g. for the controller
238 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
239 C<BackgroundJobHistory>).
240
241 =item * C<DEFAULT_BY>
242
243 Optional. A string: the index to sort by if the user hasn't clicked on
244 any column yet (meaning: if the C<$::form> parameters for sorting do
245 not contain a valid index).
246
247 Defaults to the underlying database model's default sort column name.
248
249 =item * C<DEFAULT_DIR>
250
251 Optional. Default sort direction (ascending for trueish values,
252 descrending for falsish values).
253
254 Defaults to the underlying database model's default sort direction.
255
256 =item * C<FORM_PARAMS>
257
258 Optional. An array reference with exactly two strings that name the
259 indexes in C<$::form> in which the sort index (the first element in
260 the array) and sort direction (the second element in the array) are
261 stored.
262
263 Defaults to the values C<sort_by> and C<sort_dir> if missing.
264
265 =item * C<ONLY>
266
267 Optional. An array reference containing a list of action names for
268 which the sort parameters should be saved. If missing or empty then
269 all actions invoked on the controller are monitored.
270
271 =back
272
273 All keys that are written in all lower-case name indexes that can be
274 used for sorting. Each value to such a key can be either a string or a
275 hash reference containing certain elements. If the value is only a
276 string then such a hash reference is constructed, and the string is
277 used as the value for the C<title> key.
278
279 These possible elements are:
280
281 =over 4
282
283 =item * C<title>
284
285 Required. A user-displayable title to be used by functions like the
286 layout helper's C<sortable_table_header>. Does not have a default
287 value.
288
289 Note that this string must be the untranslated English version of the
290 string. The titles will be translated whenever they're requested.
291
292 =item * C<model>
293
294 Optional. The name of a Rose database model this sort index refers
295 to. If missing then the value of C<$sort_spec{MODEL}> is used.
296
297 =item * C<model_column>
298
299 Optional. The name of the Rose database model column this sort index
300 refers to. It must be one of the columns named by the model's
301 C<Sorted> helper (not to be confused with the controller's C<Sorted>
302 helper!).
303
304 If missing it defaults to the key in C<%sort_spec> for which this hash
305 reference is the value.
306
307 =back
308
309 =back
310
311 =head1 INSTANCE FUNCTIONS
312
313 These functions are called on a controller instance.
314
315 =over 4
316
317 =item C<get_sort_spec>
318
319 Returns a hash containing the currently active sort parameters.
320
321 The key C<by> contains the active sort index referring to the
322 C<%sort_spec> given to L<make_sorted>.
323
324 The key C<dir> is either C<1> or C<0>.
325
326 =item C<get_current_sort_params>
327
328 Returns a hash reference to the sort spec structure given in the call
329 to L<make_sorted> after normalization (hash reference construction,
330 applying default parameters etc).
331
332 =item C<set_report_generator_sort_options %params>
333
334 This function does three things with an instance of
335 L<SL::ReportGenerator>:
336
337 =over 4
338
339 =item 1. it sets the sort indicator,
340
341 =item 2. it sets the the links for those column headers that are
342 sortable and
343
344 =item 3. it adds the C<FORM_PARAMS> fields to the list of variables in
345 the report generator's export options.
346
347 =back
348
349 The report generator instance must be passed as the parameter
350 C<report>. The parameter C<sortable_columns> must be an array
351 reference of column names that are sortable.
352
353 The report generator instance must already have its columns and export
354 options set via calls to its L<SL::ReportGenerator::set_columns> and
355 L<SL::ReportGenerator::set_export_options> functions.
356
357 =back
358
359 =head1 BUGS
360
361 Nothing here yet.
362
363 =head1 AUTHOR
364
365 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
366
367 =cut