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 = \%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;
 
  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;
 
 112     by            =>   $::form->{ $sort_spec->{FORM_PARAMS}->[0] },
 
 113     dir           => !!$::form->{ $sort_spec->{FORM_PARAMS}->[1] } * 1,
 
 116   # $::lxdebug->message(0, "saving current sort params to " . $self->{PRIV()}->{by} . ' / ' . $self->{PRIV()}->{dir});
 
 119 sub _callback_handler_for_sorted {
 
 120   my ($self, %params) = @_;
 
 122   my $priv = $self->{PRIV()} || {};
 
 124     my $sort_spec                             = $self->get_sort_spec;
 
 125     $params{ $sort_spec->{FORM_PARAMS}->[0] } = $priv->{by};
 
 126     $params{ $sort_spec->{FORM_PARAMS}->[1] } = $priv->{dir};
 
 129   # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
 
 134 sub _get_models_handler_for_sorted {
 
 135   my ($self, %params) = @_;
 
 137   my %sort_params     = $self->get_current_sort_params;
 
 138   my $sort_spec       = $self->get_sort_spec->{ $sort_params{by} };
 
 140   $params{model}      = $sort_spec->{model};
 
 141   $params{sort_by}    = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
 
 143   # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
 
 157 SL::Controller::Helper::Sorted - A helper for semi-automatic handling
 
 158 of sorting lists of database models in a controller
 
 164   use SL::Controller::Helper::GetModels;
 
 165   use SL::Controller::Helper::Sorted;
 
 167   __PACKAGE__->make_sorted(
 
 168     DEFAULT_BY   => 'run_at',
 
 170     MODEL        => 'BackgroundJobHistory',
 
 171     ONLY         => [ qw(list) ],
 
 173     error        => $::locale->text('Error'),
 
 174     package_name => $::locale->text('Package name'),
 
 175     run_at       => $::locale->text('Run at'),
 
 181     my $sorted_models = $self->get_models;
 
 182     $self->render('controller/list', ENTRIES => $sorted_models);
 
 191     <th>[% L.sortable_table_header('package_name') %]</th>
 
 192     <th>[% L.sortable_table_header('run_at') %]</th>
 
 193     <th>[% L.sortable_table_header('error') %]</th>
 
 196    [% FOREACH entry = ENTRIES %]
 
 198      <td>[% HTML.escape(entry.package_name) %]</td>
 
 199      <td>[% HTML.escape(entry.run_at) %]</td>
 
 200      <td>[% HTML.escape(entry.error) %]</td>
 
 207 This specialized helper module enables controllers to display a
 
 208 sortable list of database models with as few lines as possible.
 
 210 For this to work the controller has to provide the information which
 
 211 indexes are eligible for sorting etc. by a call to L<make_sorted> at
 
 214 The underlying functionality that enables the use of more than just
 
 215 the sort helper is provided by the controller helper C<GetModels>. It
 
 216 provides mechanisms for helpers like this one to hook into certain
 
 217 calls made by the controller (C<get_callback> and C<get_models>) so
 
 218 that the specialized helpers can inject their parameters into the
 
 219 calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
 
 221 A template on the other hand can use the method
 
 222 C<sortable_table_header> from the layout helper module C<L>.
 
 224 This module requires that the Rose model managers use their C<Sorted>
 
 227 The C<Sorted> helper hooks into the controller call to the action via
 
 228 a C<run_before> hook. This is done so that it can remember the sort
 
 229 parameters that were used in the current view.
 
 231 =head1 PACKAGE FUNCTIONS
 
 235 =item C<make_sorted %sort_spec>
 
 237 This function must be called by a controller at compile time. It is
 
 238 uesd to set the various parameters required for this helper to do its
 
 241 There are two sorts of keys in the hash C<%sort_spec>. The first kind
 
 242 is written in all upper-case. Those parameters are control
 
 243 parameters. The second kind are all lower-case and represent indexes
 
 244 that can be used for sorting (similar to database column names). The
 
 245 second kind are also the indexes you use in a template when calling
 
 246 C<[% L.sorted_table_header(...) %]>.
 
 248 Control parameters include the following:
 
 254 Optional. A string: the name of the Rose database model that is used
 
 255 as a default in certain cases. If this parameter is missing then it is
 
 256 derived from the controller's package (e.g. for the controller
 
 257 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
 
 258 C<BackgroundJobHistory>).
 
 260 =item * C<DEFAULT_BY>
 
 262 Optional. A string: the index to sort by if the user hasn't clicked on
 
 263 any column yet (meaning: if the C<$::form> parameters for sorting do
 
 264 not contain a valid index).
 
 266 Defaults to the underlying database model's default sort column name.
 
 268 =item * C<DEFAULT_DIR>
 
 270 Optional. Default sort direction (ascending for trueish values,
 
 271 descrending for falsish values).
 
 273 Defaults to the underlying database model's default sort direction.
 
 275 =item * C<FORM_PARAMS>
 
 277 Optional. An array reference with exactly two strings that name the
 
 278 indexes in C<$::form> in which the sort index (the first element in
 
 279 the array) and sort direction (the second element in the array) are
 
 282 Defaults to the values C<sort_by> and C<sort_dir> if missing.
 
 286 Optional. An array reference containing a list of action names for
 
 287 which the sort parameters should be saved. If missing or empty then
 
 288 all actions invoked on the controller are monitored.
 
 292 All keys that are written in all lower-case name indexes that can be
 
 293 used for sorting. Each value to such a key can be either a string or a
 
 294 hash reference containing certain elements. If the value is only a
 
 295 string then such a hash reference is constructed, and the string is
 
 296 used as the value for the C<title> key.
 
 298 These possible elements are:
 
 304 Required. A user-displayable title to be used by functions like the
 
 305 layout helper's C<sortable_table_header>. Does not have a default
 
 308 Note that this string must be the untranslated English version of the
 
 309 string. The titles will be translated whenever they're requested.
 
 313 Optional. The name of a Rose database model this sort index refers
 
 314 to. If missing then the value of C<$sort_spec{MODEL}> is used.
 
 316 =item * C<model_column>
 
 318 Optional. The name of the Rose database model column this sort index
 
 319 refers to. It must be one of the columns named by the model's
 
 320 C<Sorted> helper (not to be confused with the controller's C<Sorted>
 
 323 If missing it defaults to the key in C<%sort_spec> for which this hash
 
 324 reference is the value.
 
 330 =head1 INSTANCE FUNCTIONS
 
 332 These functions are called on a controller instance.
 
 336 =item C<get_sort_spec>
 
 338 Returns a hash containing the currently active sort parameters.
 
 340 The key C<by> contains the active sort index referring to the
 
 341 C<%sort_spec> given to L<make_sorted>.
 
 343 The key C<dir> is either C<1> or C<0>.
 
 345 =item C<get_current_sort_params>
 
 347 Returns a hash reference to the sort spec structure given in the call
 
 348 to L<make_sorted> after normalization (hash reference construction,
 
 349 applying default parameters etc).
 
 351 =item C<set_report_generator_sort_options %params>
 
 353 This function does three things with an instance of
 
 354 L<SL::ReportGenerator>:
 
 358 =item 1. it sets the sort indicator,
 
 360 =item 2. it sets the the links for those column headers that are
 
 363 =item 3. it adds the C<FORM_PARAMS> fields to the list of variables in
 
 364 the report generator's export options.
 
 368 The report generator instance must be passed as the parameter
 
 369 C<report>. The parameter C<sortable_columns> must be an array
 
 370 reference of column names that are sortable.
 
 372 The report generator instance must already have its columns and export
 
 373 options set via calls to its L<SL::ReportGenerator::set_columns> and
 
 374 L<SL::ReportGenerator::set_export_options> functions.
 
 384 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>