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