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