Bugs, Doku Stub
[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   use SL::Controller::Helper::GetModels;
147   use SL::Controller::Helper::Sorted;
148
149   __PACKAGE__->make_sorted(                                     # update this
150     DEFAULT_BY   => 'run_at',
151     DEFAULT_DIR  => 1,
152     MODEL        => 'BackgroundJobHistory',
153     ONLY         => [ qw(list) ],
154
155     error        => $::locale->text('Error'),
156     package_name => $::locale->text('Package name'),
157     run_at       => $::locale->text('Run at'),
158   );
159
160   sub action_list {
161     my ($self) = @_;
162
163     my $sorted_models = $self->get_models;
164     $self->render('controller/list', ENTRIES => $sorted_models);
165   }
166
167 In said template:
168
169   [% USE L %]
170
171   <table>
172    <tr>
173     <th>[% L.sortable_table_header('package_name') %]</th>         # models
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 specialized helper module 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. by a call to L<make_sorted> at  #not compiletime
194 compile time.
195
196 The underlying functionality that enables the use of more than just
197 the sort helper is provided by the controller helper C<GetModels>. It
198 provides mechanisms for helpers like this one to hook into certain
199 calls made by the controller (C<get_callback> and C<get_models>) so
200 that the specialized helpers can inject their parameters into the
201 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
202
203 A template on the other hand can use the method
204 C<sortable_table_header> from the layout helper module C<L>.
205
206 This module requires that the Rose model managers use their C<Sorted>
207 helper.
208
209 The C<Sorted> helper hooks into the controller call to the action via
210 a C<run_before> hook. This is done so that it can remember the sort
211 parameters that were used in the current view.
212
213 =head1 PACKAGE FUNCTIONS
214
215 =over 4
216
217 =item C<make_sorted %sort_spec>                                         # meh complete rewrite
218
219 This function must be called by a controller at compile time. It is
220 uesd to set the various parameters required for this helper to do its
221 magic.
222
223 There are two sorts of keys in the hash C<%sort_spec>. The first kind
224 is written in all upper-case. Those parameters are control
225 parameters. The second kind are all lower-case and represent indexes
226 that can be used for sorting (similar to database column names). The
227 second kind are also the indexes you use in a template when calling
228 C<[% L.sorted_table_header(...) %]>.
229
230 Control parameters include the following:
231
232 =over 4
233
234 =item * C<MODEL>
235
236 Optional. A string: the name of the Rose database model that is used
237 as a default in certain cases. If this parameter is missing then it is
238 derived from the controller's package (e.g. for the controller
239 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
240 C<BackgroundJobHistory>).
241
242 =item * C<DEFAULT_BY>
243
244 Optional. A string: the index to sort by if the user hasn't clicked on
245 any column yet (meaning: if the C<$::form> parameters for sorting do
246 not contain a valid index).
247
248 Defaults to the underlying database model's default sort column name.
249
250 =item * C<DEFAULT_DIR>
251
252 Optional. Default sort direction (ascending for trueish values,
253 descrending for falsish values).
254
255 Defaults to the underlying database model's default sort direction.
256
257 =item * C<FORM_PARAMS>
258
259 Optional. An array reference with exactly two strings that name the
260 indexes in C<$::form> in which the sort index (the first element in
261 the array) and sort direction (the second element in the array) are
262 stored.
263
264 Defaults to the values C<sort_by> and C<sort_dir> if missing.
265
266 =item * C<ONLY>
267
268 Optional. An array reference containing a list of action names for
269 which the sort parameters should be saved. If missing or empty then
270 all actions invoked on the controller are monitored.
271
272 =back
273
274 All keys that are written in all lower-case name indexes that can be
275 used for sorting. Each value to such a key can be either a string or a
276 hash reference containing certain elements. If the value is only a
277 string then such a hash reference is constructed, and the string is
278 used as the value for the C<title> key.
279
280 These possible elements are:
281
282 =over 4
283
284 =item * C<title>
285
286 Required. A user-displayable title to be used by functions like the
287 layout helper's C<sortable_table_header>. Does not have a default
288 value.
289
290 Note that this string must be the untranslated English version of the
291 string. The titles will be translated whenever they're requested.
292
293 =item * C<model>
294
295 Optional. The name of a Rose database model this sort index refers
296 to. If missing then the value of C<$sort_spec{MODEL}> is used.
297
298 =item * C<model_column>
299
300 Optional. The name of the Rose database model column this sort index
301 refers to. It must be one of the columns named by the model's
302 C<Sorted> helper (not to be confused with the controller's C<Sorted>
303 helper!).
304
305 If missing it defaults to the key in C<%sort_spec> for which this hash
306 reference is the value.
307
308 =back
309
310 =back
311
312 =head1 INSTANCE FUNCTIONS
313
314 These functions are called on a controller instance.
315
316 =over 4
317
318 =item C<get_sort_spec>
319
320 Returns a hash containing the currently active sort parameters.
321
322 The key C<by> contains the active sort index referring to the
323 C<%sort_spec> given to L<make_sorted>.
324
325 The key C<dir> is either C<1> or C<0>.
326
327 =item C<get_current_sort_params>
328
329 Returns a hash reference to the sort spec structure given in the call
330 to L<make_sorted> after normalization (hash reference construction,
331 applying default parameters etc).
332
333 =item C<set_report_generator_sort_options %params>
334
335 This function does three things with an instance of
336 L<SL::ReportGenerator>:
337
338 =over 4
339
340 =item 1. it sets the sort indicator,
341
342 =item 2. it sets the the links for those column headers that are
343 sortable and
344
345 =item 3. it adds the C<FORM_PARAMS> fields to the list of variables in
346 the report generator's export options.
347
348 =back
349
350 The report generator instance must be passed as the parameter
351 C<report>. The parameter C<sortable_columns> must be an array
352 reference of column names that are sortable.
353
354 The report generator instance must already have its columns and export
355 options set via calls to its L<SL::ReportGenerator::set_columns> and
356 L<SL::ReportGenerator::set_export_options> functions.
357
358 =back
359
360 =head1 BUGS
361
362 Nothing here yet.
363
364 =head1 AUTHOR
365
366 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
367
368 =cut