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>