+package SL::Controller::Helper::Sorted;
+
+use strict;
+
+use Exporter qw(import);
+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);
+
+my ($controller_sort_spec, $current_sort_by, $current_sort_dir);
+
+sub make_sorted {
+ my ($class, %specs) = @_;
+
+ $specs{MODEL} ||= $class->_controller_name;
+ $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
+
+ while (my ($column, $spec) = each %specs) {
+ next if $column =~ m/^[A-Z_]+$/;
+
+ $spec = $specs{$column} = { title => $spec } if !ref $spec;
+
+ $spec->{model} ||= $specs{MODEL};
+ $spec->{model_column} ||= $column;
+ }
+
+ $specs{DEFAULT_DIR} = $specs{DEFAULT_DIR} || !defined($specs{DEFAULT_DIR}) ? 1 : 0;
+ $specs{DEFAULT_BY} ||= "SL::DB::$specs{MODEL}::Manager"->_get_sort_spec($class)->{default}->[0];
+ $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ];
+ $specs{ONLY} ||= [];
+ $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY};
+
+ $controller_sort_spec = \%specs;
+
+ my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
+ $class->run_before('_save_current_sort_params', %hook_params);
+
+ SL::Controller::Helper::GetModels::register_get_models_handlers(
+ $class,
+ callback => '_callback_handler_for_sorted',
+ get_models => '_get_models_handler_for_sorted',
+ ONLY => $specs{ONLY},
+ );
+
+ # $::lxdebug->dump(0, "CONSPEC", \%specs);
+}
+
+sub get_sort_spec {
+ my ($class_or_self) = @_;
+
+ return $controller_sort_spec;
+}
+
+sub get_current_sort_params {
+ my ($self, %params) = @_;
+
+ my $sort_spec = $self->get_sort_spec;
+
+ if (!$params{sort_by}) {
+ $params{sort_by} = $current_sort_by;
+ $params{sort_dir} = $current_sort_dir;
+ }
+
+ my $by = $params{sort_by} || $sort_spec->{DEFAULT_BY};
+ my %sort_params = (
+ dir => defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{DEFAULT_DIR},
+ by => $sort_spec->{$by} ? $by : $sort_spec->{DEFAULT_BY},
+ );
+
+ return %sort_params;
+}
+
+#
+# private functions
+#
+
+sub _save_current_sort_params {
+ my ($self) = @_;
+
+ my $sort_spec = $self->get_sort_spec;
+ $current_sort_by = $::form->{ $sort_spec->{FORM_PARAMS}->[0] };
+ $current_sort_dir = !!$::form->{ $sort_spec->{FORM_PARAMS}->[1] } * 1;
+
+ # $::lxdebug->message(0, "saving current sort params to $current_sort_by / $current_sort_dir");
+}
+
+sub _callback_handler_for_sorted {
+ my ($self, %params) = @_;
+
+ if ($current_sort_by) {
+ my $sort_spec = $self->get_sort_spec;
+ $params{ $sort_spec->{FORM_PARAMS}->[0] } = $current_sort_by;
+ $params{ $sort_spec->{FORM_PARAMS}->[1] } = $current_sort_dir;
+ }
+
+ # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
+
+ return %params;
+}
+
+sub _get_models_handler_for_sorted {
+ my ($self, %params) = @_;
+
+ my %sort_params = $self->get_current_sort_params;
+ my $sort_spec = $self->get_sort_spec->{ $sort_params{by} };
+
+ $params{model} = $sort_spec->{model};
+ $params{sort_by} = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
+
+ # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
+
+ return %params;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Controller::Helper::Sorted - A helper for semi-automatic handling
+of sorting lists of database models in a controller
+
+=head1 SYNOPSIS
+
+In a controller:
+
+ use SL::Controller::Helper::GetModels;
+ use SL::Controller::Helper::Sorted;
+
+ __PACKAGE__->make_sorted(
+ DEFAULT_BY => 'run_at',
+ DEFAULT_DIR => 1,
+ MODEL => 'BackgroundJobHistory',
+ ONLY => [ qw(list) ],
+
+ error => $::locale->text('Error'),
+ package_name => $::locale->text('Package name'),
+ run_at => $::locale->text('Run at'),
+ );
+
+ sub action_list {
+ my ($self) = @_;
+
+ my $sorted_models = $self->get_sorted;
+ $self->render('controller/list', ENTRIES => $sorted_models);
+ }
+
+In said template:
+
+ [% USE L %]
+
+ <table>
+ <tr>
+ <th>[% L.sortable_table_header('package_name') %]</th>
+ <th>[% L.sortable_table_header('run_at') %]</th>
+ <th>[% L.sortable_table_header('error') %]</th>
+ </tr>
+
+ [% FOREACH entry = ENTRIES %]
+ <tr>
+ <td>[% HTML.escape(entry.package_name) %]</td>
+ <td>[% HTML.escape(entry.run_at) %]</td>
+ <td>[% HTML.escape(entry.error) %]</td>
+ </tr>
+ [% END %]
+ </table>
+
+=head1 OVERVIEW
+
+This specialized helper module enables controllers to display a
+sortable list of database models with as few lines as possible.
+
+For this to work the controller has to provide the information which
+indexes are eligible for sorting etc. by a call to L<make_sorted> at
+compile time.
+
+The underlying functionality that enables the use of more than just
+the sort helper is provided by the controller helper C<GetModels>. It
+provides mechanisms for helpers like this one to hook into certain
+calls made by the controller (C<get_callback> and C<get_models>) so
+that the specialized helpers can inject their parameters into the
+calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
+
+A template on the other hand can use the method
+C<sortable_table_header> from the layout helper module C<L>.
+
+The C<Sorted> helper hooks into the controller call to the action via
+a C<run_before> hook. This is done so that it can remember the sort
+parameters that were used in the current view.
+
+=head1 PACKAGE FUNCTIONS
+
+=over 4
+
+=item C<make_sorted %sort_spec>
+
+This function must be called by a controller at compile time. It is
+uesd to set the various parameters required for this helper to do its
+magic.
+
+There are two sorts of keys in the hash C<%sort_spec>. The first kind
+is written in all upper-case. Those parameters are control
+parameters. The second kind are all lower-case and represent indexes
+that can be used for sorting (similar to database column names). The
+second kind are also the indexes you use in a template when calling
+C<[% L.sorted_table_header(...) %]>.
+
+Control parameters include the following (all required parameters
+occur first):
+
+=over 4
+
+=item * C<DEFAULT_BY>
+
+Required. A string: the index to sort by if the user hasn't clicked on
+any column yet (meaning: if the C<$::form> parameters for sorting do
+not contain a valid index).
+
+=item * C<DEFAULT_DIR>
+
+Optional. Default sort direction (ascending for trueish values,
+descrending for falsish values).
+
+Defaults to C<1> if missing.
+
+=item * C<MODEL>
+
+Optional. A string: the name of the Rose database model that is used
+as a default in certain cases. If this parameter is missing then it is
+derived from the controller's package (e.g. for the controller
+C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
+C<BackgroundJobHistory>).
+
+=item * C<FORM_PARAMS>
+
+Optional. An array reference with exactly two strings that name the
+indexes in C<$::form> in which the sort index (the first element in
+the array) and sort direction (the second element in the array) are
+stored.
+
+Defaults to the values C<sort_by> and C<sort_dir> if missing.
+
+=item * C<ONLY>
+
+Optional. An array reference containing a list of action names for
+which the sort parameters should be saved. If missing or empty then
+all actions invoked on the controller are monitored.
+
+=back
+
+All keys that are written in all lower-case name indexes that can be
+used for sorting. Each value to such a key can be either a string or a
+hash reference containing certain elements. If the value is only a
+string then such a hash reference is constructed, and the string is
+used as the value for the C<title> key.
+
+These possible elements are:
+
+=over 4
+
+=item * C<title>
+
+Required. A user-displayable title to be used by functions like the
+layout helper's C<sortable_table_header>. Does not have a default
+value.
+
+=item * C<model>
+
+Optional. The name of a Rose database model this sort index refers
+to. If missing then the value of C<$sort_spec{MODEL}> is used.
+
+=item * C<model_column>
+
+Optional. The name of the Rose database model column this sort index
+refers to. It must be one of the columns named by the model's
+C<Sorted> helper (not to be confused with the controller's C<Sorted>
+helper!).
+
+If missing it defaults to the key in C<%sort_spec> for which this hash
+reference is the value.
+
+=back
+
+=back
+
+=head1 INSTANCE FUNCTIONS
+
+These functions are called on a controller instance.
+
+=over 4
+
+=item C<get_sort_spec>
+
+Returns a hash containing the currently active sort parameters.
+
+The key C<by> contains the active sort index referring to the
+C<%sort_spec> given to L<make_sorted>.
+
+The key C<dir> is either C<1> or C<0>.
+
+=item C<get_current_sort_params>
+
+Returns a hash reference to the sort spec structure given in the call
+to L<make_sorted> after normalization (hash reference construction,
+applying default parameters etc).
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut