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