1 package SL::Controller::Helper::Sorted;
7 use Exporter qw(import);
8 our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params set_report_generator_sort_options
9 _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted);
11 use constant PRIV => '__sortedhelperpriv';
13 my $controller_sort_spec;
16 my ($class, %specs) = @_;
18 $specs{MODEL} ||= $class->controller_name;
19 $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
21 while (my ($column, $spec) = each %specs) {
22 next if $column =~ m/^[A-Z_]+$/;
24 $spec = $specs{$column} = { title => $spec } if !ref $spec;
26 $spec->{model} ||= $specs{MODEL};
27 $spec->{model_column} ||= $column;
30 my %model_sort_spec = "SL::DB::Manager::$specs{MODEL}"->_sort_spec;
31 $specs{DEFAULT_DIR} = $specs{DEFAULT_DIR} ? 1 : defined($specs{DEFAULT_DIR}) ? $specs{DEFAULT_DIR} * 1 : $model_sort_spec{default}->[1];
32 $specs{DEFAULT_BY} ||= $model_sort_spec{default}->[0];
33 $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ];
35 $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY};
37 $controller_sort_spec = \%specs;
39 my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
40 $class->run_before('_save_current_sort_params', %hook_params);
42 SL::Controller::Helper::GetModels::register_get_models_handlers(
44 callback => '_callback_handler_for_sorted',
45 get_models => '_get_models_handler_for_sorted',
49 # $::lxdebug->dump(0, "CONSPEC", \%specs);
53 my ($class_or_self) = @_;
55 return $controller_sort_spec;
58 sub get_current_sort_params {
59 my ($self, %params) = @_;
61 my $sort_spec = $self->get_sort_spec;
63 if (!$params{sort_by}) {
64 my $priv = $self->{PRIV()} || {};
65 $params{sort_by} = $priv->{by};
66 $params{sort_dir} = $priv->{dir};
69 my $by = $params{sort_by} || $sort_spec->{DEFAULT_BY};
71 dir => defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{DEFAULT_DIR},
72 by => $sort_spec->{$by} ? $by : $sort_spec->{DEFAULT_BY},
78 sub set_report_generator_sort_options {
79 my ($self, %params) = @_;
81 $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns);
83 my %current_sort_params = $self->get_current_sort_params;
85 foreach my $col (@{ $params{sortable_columns} }) {
86 $params{report}->{columns}->{$col}->{link} = $self->get_callback(
88 sort_dir => ($current_sort_params{by} eq $col ? 1 - $current_sort_params{dir} : $current_sort_params{dir}),
92 $params{report}->set_sort_indicator($current_sort_params{by}, 1 - $current_sort_params{dir});
99 sub _save_current_sort_params {
102 my $sort_spec = $self->get_sort_spec;
104 by => $::form->{ $sort_spec->{FORM_PARAMS}->[0] },
105 dir => !!$::form->{ $sort_spec->{FORM_PARAMS}->[1] } * 1,
108 # $::lxdebug->message(0, "saving current sort params to " . $self->{PRIV()}->{by} . ' / ' . $self->{PRIV()}->{dir});
111 sub _callback_handler_for_sorted {
112 my ($self, %params) = @_;
114 my $priv = $self->{PRIV()} || {};
116 my $sort_spec = $self->get_sort_spec;
117 $params{ $sort_spec->{FORM_PARAMS}->[0] } = $priv->{by};
118 $params{ $sort_spec->{FORM_PARAMS}->[1] } = $priv->{dir};
121 # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
126 sub _get_models_handler_for_sorted {
127 my ($self, %params) = @_;
129 my %sort_params = $self->get_current_sort_params;
130 my $sort_spec = $self->get_sort_spec->{ $sort_params{by} };
132 $params{model} = $sort_spec->{model};
133 $params{sort_by} = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
135 # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
149 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
150 of sorting lists of database models in a controller
156 use SL::Controller::Helper::GetModels;
157 use SL::Controller::Helper::Sorted;
159 __PACKAGE__->make_sorted(
160 DEFAULT_BY => 'run_at',
162 MODEL => 'BackgroundJobHistory',
163 ONLY => [ qw(list) ],
165 error => $::locale->text('Error'),
166 package_name => $::locale->text('Package name'),
167 run_at => $::locale->text('Run at'),
173 my $sorted_models = $self->get_models;
174 $self->render('controller/list', ENTRIES => $sorted_models);
183 <th>[% L.sortable_table_header('package_name') %]</th>
184 <th>[% L.sortable_table_header('run_at') %]</th>
185 <th>[% L.sortable_table_header('error') %]</th>
188 [% FOREACH entry = ENTRIES %]
190 <td>[% HTML.escape(entry.package_name) %]</td>
191 <td>[% HTML.escape(entry.run_at) %]</td>
192 <td>[% HTML.escape(entry.error) %]</td>
199 This specialized helper module enables controllers to display a
200 sortable list of database models with as few lines as possible.
202 For this to work the controller has to provide the information which
203 indexes are eligible for sorting etc. by a call to L<make_sorted> at
206 The underlying functionality that enables the use of more than just
207 the sort helper is provided by the controller helper C<GetModels>. It
208 provides mechanisms for helpers like this one to hook into certain
209 calls made by the controller (C<get_callback> and C<get_models>) so
210 that the specialized helpers can inject their parameters into the
211 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
213 A template on the other hand can use the method
214 C<sortable_table_header> from the layout helper module C<L>.
216 This module requires that the Rose model managers use their C<Sorted>
219 The C<Sorted> helper hooks into the controller call to the action via
220 a C<run_before> hook. This is done so that it can remember the sort
221 parameters that were used in the current view.
223 =head1 PACKAGE FUNCTIONS
227 =item C<make_sorted %sort_spec>
229 This function must be called by a controller at compile time. It is
230 uesd to set the various parameters required for this helper to do its
233 There are two sorts of keys in the hash C<%sort_spec>. The first kind
234 is written in all upper-case. Those parameters are control
235 parameters. The second kind are all lower-case and represent indexes
236 that can be used for sorting (similar to database column names). The
237 second kind are also the indexes you use in a template when calling
238 C<[% L.sorted_table_header(...) %]>.
240 Control parameters include the following:
246 Optional. A string: the name of the Rose database model that is used
247 as a default in certain cases. If this parameter is missing then it is
248 derived from the controller's package (e.g. for the controller
249 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
250 C<BackgroundJobHistory>).
252 =item * C<DEFAULT_BY>
254 Optional. A string: the index to sort by if the user hasn't clicked on
255 any column yet (meaning: if the C<$::form> parameters for sorting do
256 not contain a valid index).
258 Defaults to the underlying database model's default sort column name.
260 =item * C<DEFAULT_DIR>
262 Optional. Default sort direction (ascending for trueish values,
263 descrending for falsish values).
265 Defaults to the underlying database model's default sort direction.
267 =item * C<FORM_PARAMS>
269 Optional. An array reference with exactly two strings that name the
270 indexes in C<$::form> in which the sort index (the first element in
271 the array) and sort direction (the second element in the array) are
274 Defaults to the values C<sort_by> and C<sort_dir> if missing.
278 Optional. An array reference containing a list of action names for
279 which the sort parameters should be saved. If missing or empty then
280 all actions invoked on the controller are monitored.
284 All keys that are written in all lower-case name indexes that can be
285 used for sorting. Each value to such a key can be either a string or a
286 hash reference containing certain elements. If the value is only a
287 string then such a hash reference is constructed, and the string is
288 used as the value for the C<title> key.
290 These possible elements are:
296 Required. A user-displayable title to be used by functions like the
297 layout helper's C<sortable_table_header>. Does not have a default
300 Note that this string must be the untranslated English version of the
301 string. The titles will be translated whenever they're requested.
305 Optional. The name of a Rose database model this sort index refers
306 to. If missing then the value of C<$sort_spec{MODEL}> is used.
308 =item * C<model_column>
310 Optional. The name of the Rose database model column this sort index
311 refers to. It must be one of the columns named by the model's
312 C<Sorted> helper (not to be confused with the controller's C<Sorted>
315 If missing it defaults to the key in C<%sort_spec> for which this hash
316 reference is the value.
322 =head1 INSTANCE FUNCTIONS
324 These functions are called on a controller instance.
328 =item C<get_sort_spec>
330 Returns a hash containing the currently active sort parameters.
332 The key C<by> contains the active sort index referring to the
333 C<%sort_spec> given to L<make_sorted>.
335 The key C<dir> is either C<1> or C<0>.
337 =item C<get_current_sort_params>
339 Returns a hash reference to the sort spec structure given in the call
340 to L<make_sorted> after normalization (hash reference construction,
341 applying default parameters etc).
343 =item C<set_report_generator_sort_options %params>
345 This function sets two things in an instance of
346 L<SL::ReportGenerator>: the sort indicator and the links for those
347 column headers that are sortable.
349 The report generator instance must be passed as the parameter
350 C<report>. The parameter C<sortable_columns> must be an array
351 reference of column names that are sortable.
353 The report generator instance must already have its columns set via a
354 call to its L<SL::ReportGenerator::set_columns> function.
364 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>