1 package SL::Controller::Helper::Sorted;
6 use List::MoreUtils qw(uniq);
8 use Exporter qw(import);
9 our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params set_report_generator_sort_options
10 _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted);
12 use constant PRIV => '__sortedhelperpriv';
14 my %controller_sort_spec;
17 my ($class, %specs) = @_;
19 $specs{MODEL} ||= $class->controller_name;
20 $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
22 while (my ($column, $spec) = each %specs) {
23 next if $column =~ m/^[A-Z_]+$/;
25 $spec = $specs{$column} = { title => $spec } if (ref($spec) || '') ne 'HASH';
27 $spec->{model} ||= $specs{MODEL};
28 $spec->{model_column} ||= $column;
31 my %model_sort_spec = "SL::DB::Manager::$specs{MODEL}"->_sort_spec;
32 $specs{DEFAULT_DIR} = $specs{DEFAULT_DIR} ? 1 : defined($specs{DEFAULT_DIR}) ? $specs{DEFAULT_DIR} * 1 : $model_sort_spec{default}->[1];
33 $specs{DEFAULT_BY} ||= $model_sort_spec{default}->[0];
34 $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ];
36 $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY};
38 $controller_sort_spec{$class} = \%specs;
40 my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
41 $class->run_before('_save_current_sort_params', %hook_params);
43 SL::Controller::Helper::GetModels::register_get_models_handlers(
45 callback => '_callback_handler_for_sorted',
46 get_models => '_get_models_handler_for_sorted',
50 # $::lxdebug->dump(0, "CONSPEC", \%specs);
54 my ($class_or_self) = @_;
56 return $controller_sort_spec{ref($class_or_self) || $class_or_self};
59 sub get_current_sort_params {
60 my ($self, %params) = @_;
62 my $sort_spec = $self->get_sort_spec;
64 if (!$params{sort_by}) {
65 my $priv = $self->{PRIV()} || {};
66 $params{sort_by} = $priv->{by};
67 $params{sort_dir} = $priv->{dir};
70 my $by = $params{sort_by} || $sort_spec->{DEFAULT_BY};
72 dir => defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{DEFAULT_DIR},
73 by => $sort_spec->{$by} ? $by : $sort_spec->{DEFAULT_BY},
79 sub set_report_generator_sort_options {
80 my ($self, %params) = @_;
82 $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns);
84 my %current_sort_params = $self->get_current_sort_params;
86 foreach my $col (@{ $params{sortable_columns} }) {
87 $params{report}->{columns}->{$col}->{link} = $self->get_callback(
89 sort_dir => ($current_sort_params{by} eq $col ? 1 - $current_sort_params{dir} : $current_sort_params{dir}),
93 $params{report}->set_sort_indicator($current_sort_params{by}, 1 - $current_sort_params{dir});
95 if ($params{report}->{export}) {
96 $params{report}->{export}->{variable_list} = [ uniq(
97 @{ $params{report}->{export}->{variable_list} },
98 @{ $self->get_sort_spec->{FORM_PARAMS} }
107 sub _save_current_sort_params {
110 my $sort_spec = $self->get_sort_spec;
111 my $dir_idx = $sort_spec->{FORM_PARAMS}->[1];
113 by => $::form->{ $sort_spec->{FORM_PARAMS}->[0] },
114 dir => defined($::form->{$dir_idx}) ? $::form->{$dir_idx} * 1 : undef,
117 # $::lxdebug->message(0, "saving current sort params to " . $self->{PRIV()}->{by} . ' / ' . $self->{PRIV()}->{dir});
120 sub _callback_handler_for_sorted {
121 my ($self, %params) = @_;
123 my $priv = $self->{PRIV()} || {};
125 my $sort_spec = $self->get_sort_spec;
126 $params{ $sort_spec->{FORM_PARAMS}->[0] } = $priv->{by};
127 $params{ $sort_spec->{FORM_PARAMS}->[1] } = $priv->{dir};
130 # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
135 sub _get_models_handler_for_sorted {
136 my ($self, %params) = @_;
138 my %sort_params = $self->get_current_sort_params;
139 my $sort_spec = $self->get_sort_spec->{ $sort_params{by} };
141 $params{model} = $sort_spec->{model};
142 $params{sort_by} = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
144 # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
158 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
159 of sorting lists of database models in a controller
165 use SL::Controller::Helper::GetModels;
166 use SL::Controller::Helper::Sorted;
168 __PACKAGE__->make_sorted(
169 DEFAULT_BY => 'run_at',
171 MODEL => 'BackgroundJobHistory',
172 ONLY => [ qw(list) ],
174 error => $::locale->text('Error'),
175 package_name => $::locale->text('Package name'),
176 run_at => $::locale->text('Run at'),
182 my $sorted_models = $self->get_models;
183 $self->render('controller/list', ENTRIES => $sorted_models);
192 <th>[% L.sortable_table_header('package_name') %]</th>
193 <th>[% L.sortable_table_header('run_at') %]</th>
194 <th>[% L.sortable_table_header('error') %]</th>
197 [% FOREACH entry = ENTRIES %]
199 <td>[% HTML.escape(entry.package_name) %]</td>
200 <td>[% HTML.escape(entry.run_at) %]</td>
201 <td>[% HTML.escape(entry.error) %]</td>
208 This specialized helper module enables controllers to display a
209 sortable list of database models with as few lines as possible.
211 For this to work the controller has to provide the information which
212 indexes are eligible for sorting etc. by a call to L<make_sorted> at
215 The underlying functionality that enables the use of more than just
216 the sort helper is provided by the controller helper C<GetModels>. It
217 provides mechanisms for helpers like this one to hook into certain
218 calls made by the controller (C<get_callback> and C<get_models>) so
219 that the specialized helpers can inject their parameters into the
220 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
222 A template on the other hand can use the method
223 C<sortable_table_header> from the layout helper module C<L>.
225 This module requires that the Rose model managers use their C<Sorted>
228 The C<Sorted> helper hooks into the controller call to the action via
229 a C<run_before> hook. This is done so that it can remember the sort
230 parameters that were used in the current view.
232 =head1 PACKAGE FUNCTIONS
236 =item C<make_sorted %sort_spec>
238 This function must be called by a controller at compile time. It is
239 uesd to set the various parameters required for this helper to do its
242 There are two sorts of keys in the hash C<%sort_spec>. The first kind
243 is written in all upper-case. Those parameters are control
244 parameters. The second kind are all lower-case and represent indexes
245 that can be used for sorting (similar to database column names). The
246 second kind are also the indexes you use in a template when calling
247 C<[% L.sorted_table_header(...) %]>.
249 Control parameters include the following:
255 Optional. A string: the name of the Rose database model that is used
256 as a default in certain cases. If this parameter is missing then it is
257 derived from the controller's package (e.g. for the controller
258 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
259 C<BackgroundJobHistory>).
261 =item * C<DEFAULT_BY>
263 Optional. A string: the index to sort by if the user hasn't clicked on
264 any column yet (meaning: if the C<$::form> parameters for sorting do
265 not contain a valid index).
267 Defaults to the underlying database model's default sort column name.
269 =item * C<DEFAULT_DIR>
271 Optional. Default sort direction (ascending for trueish values,
272 descrending for falsish values).
274 Defaults to the underlying database model's default sort direction.
276 =item * C<FORM_PARAMS>
278 Optional. An array reference with exactly two strings that name the
279 indexes in C<$::form> in which the sort index (the first element in
280 the array) and sort direction (the second element in the array) are
283 Defaults to the values C<sort_by> and C<sort_dir> if missing.
287 Optional. An array reference containing a list of action names for
288 which the sort parameters should be saved. If missing or empty then
289 all actions invoked on the controller are monitored.
293 All keys that are written in all lower-case name indexes that can be
294 used for sorting. Each value to such a key can be either a string or a
295 hash reference containing certain elements. If the value is only a
296 string then such a hash reference is constructed, and the string is
297 used as the value for the C<title> key.
299 These possible elements are:
305 Required. A user-displayable title to be used by functions like the
306 layout helper's C<sortable_table_header>. Does not have a default
309 Note that this string must be the untranslated English version of the
310 string. The titles will be translated whenever they're requested.
314 Optional. The name of a Rose database model this sort index refers
315 to. If missing then the value of C<$sort_spec{MODEL}> is used.
317 =item * C<model_column>
319 Optional. The name of the Rose database model column this sort index
320 refers to. It must be one of the columns named by the model's
321 C<Sorted> helper (not to be confused with the controller's C<Sorted>
324 If missing it defaults to the key in C<%sort_spec> for which this hash
325 reference is the value.
331 =head1 INSTANCE FUNCTIONS
333 These functions are called on a controller instance.
337 =item C<get_sort_spec>
339 Returns a hash containing the currently active sort parameters.
341 The key C<by> contains the active sort index referring to the
342 C<%sort_spec> given to L<make_sorted>.
344 The key C<dir> is either C<1> or C<0>.
346 =item C<get_current_sort_params>
348 Returns a hash reference to the sort spec structure given in the call
349 to L<make_sorted> after normalization (hash reference construction,
350 applying default parameters etc).
352 =item C<set_report_generator_sort_options %params>
354 This function does three things with an instance of
355 L<SL::ReportGenerator>:
359 =item 1. it sets the sort indicator,
361 =item 2. it sets the the links for those column headers that are
364 =item 3. it adds the C<FORM_PARAMS> fields to the list of variables in
365 the report generator's export options.
369 The report generator instance must be passed as the parameter
370 C<report>. The parameter C<sortable_columns> must be an array
371 reference of column names that are sortable.
373 The report generator instance must already have its columns and export
374 options set via calls to its L<SL::ReportGenerator::set_columns> and
375 L<SL::ReportGenerator::set_export_options> functions.
385 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>