f522d9df698f65c4cc64c50055253dd989b21e75
[kivitendo-erp.git] / SL / Controller / Helper / Sorted.pm
1 package SL::Controller::Helper::Sorted;
2
3 use strict;
4
5 use Exporter qw(import);
6 our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted);
7
8 my ($controller_sort_spec, $current_sort_by, $current_sort_dir);
9
10 sub make_sorted {
11   my ($class, %specs) = @_;
12
13   $specs{MODEL} ||=  $class->_controller_name;
14   $specs{MODEL}   =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
15
16   while (my ($column, $spec) = each %specs) {
17     next if $column =~ m/^[A-Z_]+$/;
18
19     $spec = $specs{$column} = { title => $spec } if !ref $spec;
20
21     $spec->{model}        ||= $specs{MODEL};
22     $spec->{model_column} ||= $column;
23   }
24
25   my %model_sort_spec   = "SL::DB::Manager::$specs{MODEL}"->_sort_spec;
26   $specs{DEFAULT_DIR}   = $specs{DEFAULT_DIR} ? 1 : defined($specs{DEFAULT_DIR}) ? $specs{DEFAULT_DIR} * 1 : $model_sort_spec{default}->[1];
27   $specs{DEFAULT_BY}  ||= $model_sort_spec{default}->[0];
28   $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ];
29   $specs{ONLY}        ||= [];
30   $specs{ONLY}          = [ $specs{ONLY} ] if !ref $specs{ONLY};
31
32   $controller_sort_spec = \%specs;
33
34   my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
35   $class->run_before('_save_current_sort_params', %hook_params);
36
37   SL::Controller::Helper::GetModels::register_get_models_handlers(
38     $class,
39     callback   => '_callback_handler_for_sorted',
40     get_models => '_get_models_handler_for_sorted',
41     ONLY       => $specs{ONLY},
42   );
43
44   # $::lxdebug->dump(0, "CONSPEC", \%specs);
45 }
46
47 sub get_sort_spec {
48   my ($class_or_self) = @_;
49
50   return $controller_sort_spec;
51 }
52
53 sub get_current_sort_params {
54   my ($self, %params) = @_;
55
56   my $sort_spec = $self->get_sort_spec;
57
58   if (!$params{sort_by}) {
59     $params{sort_by}  = $current_sort_by;
60     $params{sort_dir} = $current_sort_dir;
61   }
62
63   my $by          = $params{sort_by} || $sort_spec->{DEFAULT_BY};
64   my %sort_params = (
65     dir => defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{DEFAULT_DIR},
66     by  => $sort_spec->{$by} ? $by : $sort_spec->{DEFAULT_BY},
67   );
68
69   return %sort_params;
70 }
71
72 #
73 # private functions
74 #
75
76 sub _save_current_sort_params {
77   my ($self) = @_;
78
79   my $sort_spec     = $self->get_sort_spec;
80   $current_sort_by  =   $::form->{ $sort_spec->{FORM_PARAMS}->[0] };
81   $current_sort_dir = !!$::form->{ $sort_spec->{FORM_PARAMS}->[1] } * 1;
82
83   # $::lxdebug->message(0, "saving current sort params to $current_sort_by / $current_sort_dir");
84 }
85
86 sub _callback_handler_for_sorted {
87   my ($self, %params) = @_;
88
89   if ($current_sort_by) {
90     my $sort_spec                             = $self->get_sort_spec;
91     $params{ $sort_spec->{FORM_PARAMS}->[0] } = $current_sort_by;
92     $params{ $sort_spec->{FORM_PARAMS}->[1] } = $current_sort_dir;
93   }
94
95   # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
96
97   return %params;
98 }
99
100 sub _get_models_handler_for_sorted {
101   my ($self, %params) = @_;
102
103   my %sort_params     = $self->get_current_sort_params;
104   my $sort_spec       = $self->get_sort_spec->{ $sort_params{by} };
105
106   $params{model}      = $sort_spec->{model};
107   $params{sort_by}    = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
108
109   # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
110
111   return %params;
112 }
113
114 1;
115 __END__
116
117 =pod
118
119 =encoding utf8
120
121 =head1 NAME
122
123 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
124 of sorting lists of database models in a controller
125
126 =head1 SYNOPSIS
127
128 In a controller:
129
130   use SL::Controller::Helper::GetModels;
131   use SL::Controller::Helper::Sorted;
132
133   __PACKAGE__->make_sorted(
134     DEFAULT_BY   => 'run_at',
135     DEFAULT_DIR  => 1,
136     MODEL        => 'BackgroundJobHistory',
137     ONLY         => [ qw(list) ],
138
139     error        => $::locale->text('Error'),
140     package_name => $::locale->text('Package name'),
141     run_at       => $::locale->text('Run at'),
142   );
143
144   sub action_list {
145     my ($self) = @_;
146
147     my $sorted_models = $self->get_models;
148     $self->render('controller/list', ENTRIES => $sorted_models);
149   }
150
151 In said template:
152
153   [% USE L %]
154
155   <table>
156    <tr>
157     <th>[% L.sortable_table_header('package_name') %]</th>
158     <th>[% L.sortable_table_header('run_at') %]</th>
159     <th>[% L.sortable_table_header('error') %]</th>
160    </tr>
161
162    [% FOREACH entry = ENTRIES %]
163     <tr>
164      <td>[% HTML.escape(entry.package_name) %]</td>
165      <td>[% HTML.escape(entry.run_at) %]</td>
166      <td>[% HTML.escape(entry.error) %]</td>
167     </tr>
168    [% END %]
169   </table>
170
171 =head1 OVERVIEW
172
173 This specialized helper module enables controllers to display a
174 sortable list of database models with as few lines as possible.
175
176 For this to work the controller has to provide the information which
177 indexes are eligible for sorting etc. by a call to L<make_sorted> at
178 compile time.
179
180 The underlying functionality that enables the use of more than just
181 the sort helper is provided by the controller helper C<GetModels>. It
182 provides mechanisms for helpers like this one to hook into certain
183 calls made by the controller (C<get_callback> and C<get_models>) so
184 that the specialized helpers can inject their parameters into the
185 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
186
187 A template on the other hand can use the method
188 C<sortable_table_header> from the layout helper module C<L>.
189
190 This module requires that the Rose model managers use their C<Sorted>
191 helper.
192
193 The C<Sorted> helper hooks into the controller call to the action via
194 a C<run_before> hook. This is done so that it can remember the sort
195 parameters that were used in the current view.
196
197 =head1 PACKAGE FUNCTIONS
198
199 =over 4
200
201 =item C<make_sorted %sort_spec>
202
203 This function must be called by a controller at compile time. It is
204 uesd to set the various parameters required for this helper to do its
205 magic.
206
207 There are two sorts of keys in the hash C<%sort_spec>. The first kind
208 is written in all upper-case. Those parameters are control
209 parameters. The second kind are all lower-case and represent indexes
210 that can be used for sorting (similar to database column names). The
211 second kind are also the indexes you use in a template when calling
212 C<[% L.sorted_table_header(...) %]>.
213
214 Control parameters include the following:
215
216 =over 4
217
218 =item * C<MODEL>
219
220 Optional. A string: the name of the Rose database model that is used
221 as a default in certain cases. If this parameter is missing then it is
222 derived from the controller's package (e.g. for the controller
223 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
224 C<BackgroundJobHistory>).
225
226 =item * C<DEFAULT_BY>
227
228 Optional. A string: the index to sort by if the user hasn't clicked on
229 any column yet (meaning: if the C<$::form> parameters for sorting do
230 not contain a valid index).
231
232 Defaults to the underlying database model's default sort column name.
233
234 =item * C<DEFAULT_DIR>
235
236 Optional. Default sort direction (ascending for trueish values,
237 descrending for falsish values).
238
239 Defaults to the underlying database model's default sort direction.
240
241 =item * C<FORM_PARAMS>
242
243 Optional. An array reference with exactly two strings that name the
244 indexes in C<$::form> in which the sort index (the first element in
245 the array) and sort direction (the second element in the array) are
246 stored.
247
248 Defaults to the values C<sort_by> and C<sort_dir> if missing.
249
250 =item * C<ONLY>
251
252 Optional. An array reference containing a list of action names for
253 which the sort parameters should be saved. If missing or empty then
254 all actions invoked on the controller are monitored.
255
256 =back
257
258 All keys that are written in all lower-case name indexes that can be
259 used for sorting. Each value to such a key can be either a string or a
260 hash reference containing certain elements. If the value is only a
261 string then such a hash reference is constructed, and the string is
262 used as the value for the C<title> key.
263
264 These possible elements are:
265
266 =over 4
267
268 =item * C<title>
269
270 Required. A user-displayable title to be used by functions like the
271 layout helper's C<sortable_table_header>. Does not have a default
272 value.
273
274 =item * C<model>
275
276 Optional. The name of a Rose database model this sort index refers
277 to. If missing then the value of C<$sort_spec{MODEL}> is used.
278
279 =item * C<model_column>
280
281 Optional. The name of the Rose database model column this sort index
282 refers to. It must be one of the columns named by the model's
283 C<Sorted> helper (not to be confused with the controller's C<Sorted>
284 helper!).
285
286 If missing it defaults to the key in C<%sort_spec> for which this hash
287 reference is the value.
288
289 =back
290
291 =back
292
293 =head1 INSTANCE FUNCTIONS
294
295 These functions are called on a controller instance.
296
297 =over 4
298
299 =item C<get_sort_spec>
300
301 Returns a hash containing the currently active sort parameters.
302
303 The key C<by> contains the active sort index referring to the
304 C<%sort_spec> given to L<make_sorted>.
305
306 The key C<dir> is either C<1> or C<0>.
307
308 =item C<get_current_sort_params>
309
310 Returns a hash reference to the sort spec structure given in the call
311 to L<make_sorted> after normalization (hash reference construction,
312 applying default parameters etc).
313
314 =back
315
316 =head1 BUGS
317
318 Nothing here yet.
319
320 =head1 AUTHOR
321
322 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
323
324 =cut