From: Sven Schöling Date: Mon, 16 Sep 2013 17:12:50 +0000 (+0200) Subject: Erste Version GetModels rewrite X-Git-Tag: release-3.1.0beta1~50 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=ec3a4636c1d58339915614120cd82759150d7641;p=kivitendo-erp.git Erste Version GetModels rewrite known bugs: disable pagination funktioniert nicht compiletime optimizations werden noch nicht benutzt doku fehlt --- diff --git a/SL/Controller/DeliveryPlan.pm b/SL/Controller/DeliveryPlan.pm index b8ee1c41d..7d394bebf 100644 --- a/SL/Controller/DeliveryPlan.pm +++ b/SL/Controller/DeliveryPlan.pm @@ -6,34 +6,43 @@ use parent qw(SL::Controller::Base); use Clone qw(clone); use SL::DB::OrderItem; use SL::Controller::Helper::GetModels; -use SL::Controller::Helper::Paginated; -use SL::Controller::Helper::Sorted; -use SL::Controller::Helper::Filtered; use SL::Controller::Helper::ReportGenerator; use SL::Locale::String; use Rose::Object::MakeMethods::Generic ( scalar => [ qw(db_args flat_filter) ], + 'scalar --get_set_init' => [ qw(models) ], ); __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); }); -__PACKAGE__->make_filtered( - MODEL => 'OrderItem', - LAUNDER_TO => 'filter' -); -__PACKAGE__->make_paginated( - MODEL => 'OrderItem', - ONLY => [ qw(list) ], -); - -__PACKAGE__->make_sorted( - MODEL => 'OrderItem', - ONLY => [ qw(list) ], - - DEFAULT_BY => 'reqdate', - DEFAULT_DIR => 1, - +#__PACKAGE__->make_filtered( +# MODEL => 'OrderItem', +# LAUNDER_TO => 'filter' +#); +#__PACKAGE__->make_paginated( +# MODEL => 'OrderItem', +# ONLY => [ qw(list) ], +#); +# +#__PACKAGE__->make_sorted( +# MODEL => 'OrderItem', +# ONLY => [ qw(list) ], +# +# DEFAULT_BY => 'reqdate', +# DEFAULT_DIR => 1, +# +# reqdate => t8('Reqdate'), +# description => t8('Description'), +# partnumber => t8('Part Number'), +# qty => t8('Qty'), +# shipped_qty => t8('shipped'), +# not_shipped_qty => t8('not shipped'), +# ordnumber => t8('Order'), +# customer => t8('Customer'), +#); + +my %sort_columns = ( reqdate => t8('Reqdate'), description => t8('Description'), partnumber => t8('Part Number'), @@ -121,7 +130,7 @@ sub action_list { $self->make_filter_summary; - my $orderitems = $self->get_models(query => $delivery_plan_query, with_objects => [ 'order', 'order.customer', 'part' ]); + my $orderitems = $self->models->get; $self->prepare_report; $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems); @@ -149,11 +158,11 @@ sub prepare_report { not_shipped_qty => { sub => sub { $::form->format_amount(\%::myconfig, $_[0]->qty - $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit } }, ordnumber => { sub => sub { $_[0]->order->ordnumber }, obj_link => sub { $self->link_to($_[0]->order) } }, - customer => { sub => sub { $_[0]->order->customer->name }, + customer => { sub => sub { return ''; $_[0]->order->customer->name }, obj_link => sub { $self->link_to($_[0]->order->customer) } }, ); - map { $column_defs{$_}->{text} = $::locale->text( $self->get_sort_spec->{$_}->{title} ) } keys %column_defs; + $column_defs{$_}->{text} = $sort_columns{$_} for keys %column_defs; $report->set_options( std_column_visibility => 1, @@ -161,7 +170,7 @@ sub prepare_report { output_format => 'HTML', top_info_text => $::locale->text('Delivery Plan for currently outstanding sales orders'), raw_top_info_text => $self->render('delivery_plan/report_top', { output => 0 }), - raw_bottom_info_text => $self->render('delivery_plan/report_bottom', { output => 0 }), + raw_bottom_info_text => $self->render('delivery_plan/report_bottom', { output => 0 }, models => $self->models), title => $::locale->text('Delivery Plan'), allow_pdf_export => 1, allow_csv_export => 1, @@ -170,9 +179,9 @@ sub prepare_report { $report->set_column_order(@columns); $report->set_export_options(qw(list filter)); $report->set_options_from_form; - $self->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable); + $self->models->sorted->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable); - $self->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i; + $self->models->paginated->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i; } sub make_filter_summary { @@ -209,6 +218,27 @@ sub make_filter_summary { $self->{filter_summary} = join ', ', @filter_strings; } +sub init_models { + my ($self) = @_; + + SL::Controller::Helper::GetModels->new( + controller => $self, + model => 'OrderItem', # defaults to controller + filtered => { + launder_to => 'filter', + }, + sorted => { + _default => { + by => 'reqdate', + dir => 1, + }, + %sort_columns, + }, + query => $delivery_plan_query, + with_objects => [ 'order', 'order.customer', 'part' ], + ); +} + sub link_to { my ($self, $object, %params) = @_; diff --git a/SL/Controller/Helper/Filtered.pm b/SL/Controller/Helper/Filtered.pm deleted file mode 100644 index 164e5edd2..000000000 --- a/SL/Controller/Helper/Filtered.pm +++ /dev/null @@ -1,309 +0,0 @@ -package SL::Controller::Helper::Filtered; - -use strict; - -use Exporter qw(import); -use SL::Controller::Helper::ParseFilter (); -use List::MoreUtils qw(uniq); -our @EXPORT = qw(make_filtered get_filter_spec get_current_filter_params disable_filtering _save_current_filter_params _callback_handler_for_filtered _get_models_handler_for_filtered); - -use constant PRIV => '__filteredhelper_priv'; - -my %controller_filter_spec; - -sub make_filtered { - my ($class, %specs) = @_; - - $specs{MODEL} //= $class->controller_name; - $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x; - $specs{FORM_PARAMS} //= 'filter'; - $specs{LAUNDER_TO} = '__INPLACE__' unless exists $specs{LAUNDER_TO}; - $specs{ONLY} //= []; - $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY}; - $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 }; - - $controller_filter_spec{$class} = \%specs; - - my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : (); - $class->run_before('_save_current_filter_params', %hook_params); - - SL::Controller::Helper::GetModels::register_get_models_handlers( - $class, - callback => '_callback_handler_for_filtered', - get_models => '_get_models_handler_for_filtered', - ONLY => $specs{ONLY}, - ); - - # $::lxdebug->dump(0, "CONSPEC", \%specs); -} - -sub get_filter_spec { - my ($class_or_self) = @_; - - return $controller_filter_spec{ref($class_or_self) || $class_or_self}; -} - -sub get_current_filter_params { - my ($self) = @_; - - return %{ _priv($self)->{filter_params} } if _priv($self)->{filter_params}; - - require Carp; - Carp::confess('It seems a GetModels plugin tries to access filter params before they got calculated. Make sure your make_filtered call comes first.'); -} - -sub _make_current_filter_params { - my ($self, %params) = @_; - - my $spec = $self->get_filter_spec; - my $filter = $params{filter} // _priv($self)->{filter} // {}, - my %filter_args = _get_filter_args($self, $spec); - my %parse_filter_args = ( - class => "SL::DB::Manager::$spec->{MODEL}", - with_objects => $params{with_objects}, - ); - my $laundered; - if ($spec->{LAUNDER_TO} eq '__INPLACE__') { - - } elsif ($spec->{LAUNDER_TO}) { - $laundered = {}; - $parse_filter_args{launder_to} = $laundered; - } else { - $parse_filter_args{no_launder} = 1; - } - - my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args); - - $calculated_params{query} = [ - @{ $calculated_params{query} || [] }, - @{ $filter_args{ query} || [] }, - @{ $params{ query} || [] }, - ]; - - $calculated_params{with_objects} = [ - uniq - @{ $calculated_params{with_objects} || [] }, - @{ $filter_args{ with_objects} || [] }, - @{ $params{ with_objects} || [] }, - ]; - - if ($laundered) { - if ($self->can($spec->{LAUNDER_TO})) { - $self->${\ $spec->{LAUNDER_TO} }($laundered); - } else { - $self->{$spec->{LAUNDER_TO}} = $laundered; - } - } - - # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params); - - _priv($self)->{filter_params} = \%calculated_params; - - return %calculated_params; -} - -sub disable_filtering { - my ($self) = @_; - _priv($self)->{disabled} = 1; -} - -# -# private functions -# - -sub _get_filter_args { - my ($self, $spec) = @_; - - $spec ||= $self->get_filter_spec; - - my %filter_args = ref($spec->{FILTER_ARGS}) eq 'CODE' ? %{ $spec->{FILTER_ARGS}->($self) } - : $spec->{FILTER_ARGS} ? do { my $sub = $spec->{FILTER_ARGS}; %{ $self->$sub() } } - : (); -} - -sub _save_current_filter_params { - my ($self) = @_; - - return if !_is_enabled($self); - - my $filter_spec = $self->get_filter_spec; - $self->{PRIV()}{filter} = $::form->{ $filter_spec->{FORM_PARAMS} }; - - # $::lxdebug->message(0, "saving current filter params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page}); -} - -sub _callback_handler_for_filtered { - my ($self, %params) = @_; - my $priv = _priv($self); - - if (_is_enabled($self) && $priv->{filter}) { - my $filter_spec = $self->get_filter_spec; - my ($flattened) = SL::Controller::Helper::ParseFilter::flatten($priv->{filter}, $filter_spec->{FORM_PARAMS}); - %params = (%params, @$flattened); - } - - # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params); - - return %params; -} - -sub _get_models_handler_for_filtered { - my ($self, %params) = @_; - my $spec = $self->get_filter_spec; - - # $::lxdebug->dump(0, "params in get_models_for_filtered", \%params); - - my %filter_params; - %filter_params = _make_current_filter_params($self, %params) if _is_enabled($self); - - # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params); - - return (%params, %filter_params); -} - -sub _priv { - my ($self) = @_; - $self->{PRIV()} ||= {}; - return $self->{PRIV()}; -} - -sub _is_enabled { - my ($self) = @_; - return !_priv($self)->{disabled} && ($self->get_filter_spec->{ONLY_MAP}->{$self->action_name} || $self->get_filter_spec->{ONLY_MAP}->{'__ALL__'}); -} - -1; - -__END__ - -=pod - -=encoding utf8 - -=head1 NAME - -SL::Controller::Helper::Filtered - A helper for semi-automatic handling -of filtered lists of database models in a controller - -=head1 SYNOPSIS - -In a controller: - - use SL::Controller::Helper::GetModels; - use SL::Controller::Helper::Filtered; - - __PACKAGE__->make_filter( - MODEL => 'Part', - ONLY => [ qw(list) ], - FORM_PARAMS => [ qw(filter) ], - ); - - sub action_list { - my ($self) = @_; - - my $filtered_models = $self->get_models(%addition_filters); - $self->render('controller/list', ENTRIES => $filtered_models); - } - - -=head1 OVERVIEW - -This helper module enables use of the L -methods in conjunction with the L style of -plugins. Additional filters can be defined in the database models and filtering -can be reduced to a minimum of work. - -This plugin can be combined with L and -L for filtered, sorted and paginated lists. - -The controller has to provive information where to look for filter information -at compile time. This call is L. - -The underlying functionality that enables the use of more than just -the paginate helper is provided by the controller helper -C. See the documentation for L for -more information on it. - -=head1 PACKAGE FUNCTIONS - -=over 4 - -=item C - -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. - -Careful: If you want to use this in conjunction with -L, you need to call C first, -or the paginating will not get all the relevant information to estimate the -number of pages correctly. To ensure this does not happen, this module will -croak when it detects such a scenario. - -The hash C<%filter_spec> can include the following parameters: - -=over 4 - -=item * C - -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 the C would default to -C). - -=item * C - -Optional. Indicates a key in C<$::form> to be used as filter. - -Defaults to the values C if missing. - -=item * C - -Option. Indicates a target for laundered filter arguments in the controller. -Can be set to C to disable laundering, and can be set to method named or -hash keys of the controller. In the latter case the laundered structure will be -put there. - -Defaults to inplace laundering which is not normally settable. - -=item * C - -Optional. An array reference containing a list of action names for -which the paginate parameters should be saved. If missing or empty then -all actions invoked on the controller are monitored. - -=back - -=back - -=head1 INSTANCE FUNCTIONS - -These functions are called on a controller instance. - -=over 4 - -=item C - -Returns a hash to be used in manager C calls or to be passed on to -GetModels. Will only work if the get_models chain has been called at least -once, because only then the full parameters can get parsed and stored. Will -croak otherwise. - -=item C - -Disable filtering for the duration of the current action. Can be used -when using the attribute C to L does not -cover all cases. - -=back - -=head1 BUGS - -Nothing here yet. - -=head1 AUTHOR - -Sven Schöling Es.schoeling@linet-services.deE - -=cut diff --git a/SL/Controller/Helper/GetModels.pm b/SL/Controller/Helper/GetModels.pm index 821d7a194..ae9810b5c 100644 --- a/SL/Controller/Helper/GetModels.pm +++ b/SL/Controller/Helper/GetModels.pm @@ -2,21 +2,48 @@ package SL::Controller::Helper::GetModels; use strict; -use Exporter qw(import); -our @EXPORT = qw(get_models_url_params get_callback get_models); +use parent 'Rose::Object'; +use SL::Controller::Helper::GetModels::Filtered; +use SL::Controller::Helper::GetModels::Sorted; +use SL::Controller::Helper::GetModels::Paginated; + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(controller model query with_objects filtered sorted paginated) ], + 'scalar --get_set_init' => [ qw(handlers) ], +); use constant PRIV => '__getmodelshelperpriv'; -my $registered_handlers = {}; +#my $registered_handlers = {}; + +sub init { + my ($self, %params) = @_; + +# for my $plugin (qw(filtered sorted paginated)) { +# next unless $params{$plugin}; +# $self->${ \"make_$plugin" }(%{ delete $params{$plugin} || {} }); +# } +# + # TODO: default model + $self->model(delete $params{model}); + + for my $plugin (qw(filtered sorted paginated)) { + next unless my $spec = delete $params{$plugin} // {}; + my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin; + $self->$plugin($plugin_class->new(%$spec, get_models => $self)); + } + + $self->SUPER::init(%params); +} -sub register_get_models_handlers { - my ($class, %additional_handlers) = @_; +sub register_handlers { + my ($self, %additional_handlers) = @_; - my $only = delete($additional_handlers{ONLY}) || []; - $only = [ $only ] if !ref $only; - my %hook_params = @{ $only } ? ( only => $only ) : (); +# my $only = delete($additional_handlers{ONLY}) || []; +# $only = [ $only ] if !ref $only; +# my %hook_params = @{ $only } ? ( only => $only ) : (); - my $handlers = _registered_handlers($class); + my $handlers = $self->handlers; map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers; } @@ -39,19 +66,34 @@ sub get_models_url_params { sub get_callback { my ($self, %override_params) = @_; - my %default_params = _run_handlers($self, 'callback', action => $self->action_name); + my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name); - return $self->url_for(%default_params, %override_params); + return $self->controller->url_for(%default_params, %override_params); } -sub get_models { - my ($self, %override_params) = @_; +sub get { + my ($self, %params) = @_; + + push @{ $params{query} ||= [] }, @{ $self->query || [] }; + push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; + + %params = $self->_run_handlers('get_models', %params); + + return $self->manager->get_all(%params); +} + +sub get_paginate_args { + my ($self, %params) = @_; - my %params = _run_handlers($self, 'get_models', %override_params); + push @{ $params{query} ||= [] }, @{ $self->query || [] }; + push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; - my $model = delete($params{model}) || die "No 'model' to work on"; + $self->paginated->get_current_paginate_params(%params); +} - return "SL::DB::Manager::${model}"->get_all(%params); +sub manager { + die "No 'model' to work on" unless $_[0]->model; + "SL::DB::Manager::" . $_[0]->model; } # @@ -61,7 +103,7 @@ sub get_models { sub _run_handlers { my ($self, $handler_type, %params) = @_; - foreach my $sub (@{ _registered_handlers(ref $self)->{$handler_type} }) { + foreach my $sub (@{ $self->handlers->{$handler_type} }) { if (ref $sub eq 'CODE') { %params = $sub->($self, %params); } elsif ($self->can($sub)) { @@ -74,8 +116,11 @@ sub _run_handlers { return %params; } -sub _registered_handlers { - $registered_handlers->{$_[0]} //= { callback => [], get_models => [] } +sub init_handlers { + { + callback => [], + get_models => [], + } } 1; diff --git a/SL/Controller/Helper/GetModels/Base.pm b/SL/Controller/Helper/GetModels/Base.pm new file mode 100644 index 000000000..3d28de5d1 --- /dev/null +++ b/SL/Controller/Helper/GetModels/Base.pm @@ -0,0 +1,29 @@ +package SL::Controller::Helper::GetModels::Base; + +use strict; +use parent 'Rose::Object'; +use Scalar::Util qw(weaken); + + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(get_models) ], +); + +sub set_get_models { + $_[0]->get_models($_[1]); + + weaken($_[1]); +} + +sub merge_args { + my ($self, @args) = @_; + my $final_args = { }; + + for my $field (qw(query with_objects)) { + $final_args->{$field} = [ map { @{ $_->{$field} || [] } } @args ]; + } + + return %$final_args; +} + +1; diff --git a/SL/Controller/Helper/GetModels/Filtered.pm b/SL/Controller/Helper/GetModels/Filtered.pm new file mode 100644 index 000000000..3374471cc --- /dev/null +++ b/SL/Controller/Helper/GetModels/Filtered.pm @@ -0,0 +1,279 @@ +package SL::Controller::Helper::GetModels::Filtered; + +use strict; +use parent 'SL::Controller::Helper::GetModels::Base'; + +use Exporter qw(import); +use SL::Controller::Helper::ParseFilter (); +use List::MoreUtils qw(uniq); + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(disabled filter_args filter_params) ], + 'scalar --get_set_init' => [ qw(form_params launder_to) ], +); + +sub init { + my ($self, %specs) = @_; + + $self->set_get_models(delete $specs{get_models}); + $self->SUPER::init(%specs); + + $self->get_models->register_handlers( + callback => sub { shift; $self->_callback_handler_for_filtered(@_) }, + get_models => sub { shift; $self->_get_models_handler_for_filtered(@_) }, + ); + + # $::lxdebug->dump(0, "CONSPEC", \%specs); +} + +sub get_current_filter_params { + my ($self) = @_; + + return $self->filter_params if $self->filter_params; + + require Carp; + Carp::confess('It seems a GetModels plugin tries to access filter params before they got calculated. Make sure your make_filtered call comes first.'); +} + +sub _make_current_filter_params { + my ($self, %params) = @_; + +# my $spec = $self->get_filter_spec; + my $filter = $params{filter} // $::form->{ $self->form_params } // {}, + my %filter_args = $self->_get_filter_args; + my %parse_filter_args = ( + class => $self->get_models->manager, + with_objects => $params{with_objects}, + ); + my $laundered; + if ($self->launder_to eq '__INPLACE__') { + # nothing to do + } elsif ($self->launder_to) { + $laundered = {}; + $parse_filter_args{launder_to} = $laundered; + } else { + $parse_filter_args{no_launder} = 1; + } + + my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args); + %calculated_params = $self->merge_args(\%calculated_params, \%filter_args, \%params); + +# $calculated_params{query} = [ +# @{ $calculated_params{query} || [] }, +# @{ $filter_args{ query} || [] }, +# @{ $params{ query} || [] }, +# ]; +# +# $calculated_params{with_objects} = [ +# uniq +# @{ $calculated_params{with_objects} || [] }, +# @{ $filter_args{ with_objects} || [] }, +# @{ $params{ with_objects} || [] }, +# ]; + + if ($laundered) { + if ($self->get_models->controller->can($self->launder_to)) { + $self->get_models->controller->${\ $self->launder_to }($laundered); + } else { + $self->get_models->controller->{$self->launder_to} = $laundered; + } + } + + # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params); + + $self->filter_params(\%calculated_params); + + return %calculated_params; +} + +sub disable_filtering { + my ($self) = @_; + $self->disabled(1); +} + +# +# private functions +# + +sub _get_filter_args { + my ($self, $spec) = @_; + + my %filter_args = ref($self->filter_args) eq 'CODE' ? %{ $self->filter_args->($self) } + : $self->filter_args ? do { my $sub = $self->filter_args; %{ $self->get_models->controller->$sub() } } + : (); +} + +sub _callback_handler_for_filtered { + my ($self, %params) = @_; + + if ($self->is_enabled) { + my ($flattened) = SL::Controller::Helper::ParseFilter::flatten($::form->{ $self->form_params }, $self->form_params); + %params = (%params, @{ $flattened || [] }); + } + + # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params); + + return %params; +} + +sub _get_models_handler_for_filtered { + my ($self, %params) = @_; + + # $::lxdebug->dump(0, "params in get_models_for_filtered", \%params); + + my %filter_params; + %filter_params = $self->_make_current_filter_params(%params) if $self->is_enabled; + + # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . $self->is_enabled . ")", \%params); + + return (%params, %filter_params); +} + +sub is_enabled { + !$_[0]->disabled; +} + +sub init_form_params { + 'filter' +} + +sub init_launder_to { + 'filter' +} + + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::Controller::Helper::Filtered - A helper for semi-automatic handling +of filtered lists of database models in a controller + +=head1 SYNOPSIS + +In a controller: + + use SL::Controller::Helper::GetModels; + use SL::Controller::Helper::Filtered; + + __PACKAGE__->make_filter( + MODEL => 'Part', + ONLY => [ qw(list) ], + FORM_PARAMS => [ qw(filter) ], + ); + + sub action_list { + my ($self) = @_; + + my $filtered_models = $self->get_models(%addition_filters); + $self->render('controller/list', ENTRIES => $filtered_models); + } + + +=head1 OVERVIEW + +This helper module enables use of the L +methods in conjunction with the L style of +plugins. Additional filters can be defined in the database models and filtering +can be reduced to a minimum of work. + +This plugin can be combined with L and +L for filtered, sorted and paginated lists. + +The controller has to provive information where to look for filter information +at compile time. This call is L. + +The underlying functionality that enables the use of more than just +the paginate helper is provided by the controller helper +C. See the documentation for L for +more information on it. + +=head1 PACKAGE FUNCTIONS + +=over 4 + +=item C + +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. + +Careful: If you want to use this in conjunction with +L, you need to call C first, +or the paginating will not get all the relevant information to estimate the +number of pages correctly. To ensure this does not happen, this module will +croak when it detects such a scenario. + +The hash C<%filter_spec> can include the following parameters: + +=over 4 + +=item * C + +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 the C would default to +C). + +=item * C + +Optional. Indicates a key in C<$::form> to be used as filter. + +Defaults to the values C if missing. + +=item * C + +Option. Indicates a target for laundered filter arguments in the controller. +Can be set to C to disable laundering, and can be set to method named or +hash keys of the controller. In the latter case the laundered structure will be +put there. + +Defaults to inplace laundering which is not normally settable. + +=item * C + +Optional. An array reference containing a list of action names for +which the paginate parameters should be saved. If missing or empty then +all actions invoked on the controller are monitored. + +=back + +=back + +=head1 INSTANCE FUNCTIONS + +These functions are called on a controller instance. + +=over 4 + +=item C + +Returns a hash to be used in manager C calls or to be passed on to +GetModels. Will only work if the get_models chain has been called at least +once, because only then the full parameters can get parsed and stored. Will +croak otherwise. + +=item C + +Disable filtering for the duration of the current action. Can be used +when using the attribute C to L does not +cover all cases. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Sven Schöling Es.schoeling@linet-services.deE + +=cut diff --git a/SL/Controller/Helper/GetModels/Paginated.pm b/SL/Controller/Helper/GetModels/Paginated.pm new file mode 100644 index 000000000..a40acaef9 --- /dev/null +++ b/SL/Controller/Helper/GetModels/Paginated.pm @@ -0,0 +1,316 @@ +package SL::Controller::Helper::GetModels::Paginated; + +use strict; +use parent 'SL::Controller::Helper::GetModels::Base'; + +use List::Util qw(min); + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(disabled per_page) ], + 'scalar --get_set_init' => [ qw(form_params paginate_args) ], +); + +sub init { + my ($self, %specs) = @_; + + $self->set_get_models(delete $specs{get_models}); + $self->SUPER::init(%specs); + + $self->per_page($self->get_models->manager->default_objects_per_page) unless $self->per_page; + + $self->get_models->register_handlers( + callback => sub { shift; $self->_callback_handler_for_paginated(@_) }, + get_models => sub { shift; $self->_get_models_handler_for_paginated(@_) }, + ); + + # $::lxdebug->dump(0, "CONSPEC", \%specs); +} + +sub get_current_paginate_params { + my ($self, %args) = @_; + return () unless $self->is_enabled; + + my %paginate_params = $self->final_params(%args); + + # try to use Filtered if available and nothing else is configured, but don't + # blow up if the controller does not use Filtered + my %paginate_args = ref($self->paginate_args) eq 'CODE' ? %{ $self->paginate_args->($self) } + : $self->paginate_args eq '__FILTER__' + && $self->get_models->filtered ? %{ $self->get_models->filtered->get_current_filter_params } + : $self->paginate_args ne '__FILTER__' ? do { my $sub = $self->paginate_args; %{ $self->get_models->controller->$sub() } } + : (); + + %args = $self->merge_args(\%args, \%paginate_args); + + my $calculated_params = $self->get_models->manager->paginate(%paginate_params, args => \%args); + + # $::lxdebug->dump(0, "get_current_paginate_params: ", $calculated_params); + + return %{ $calculated_params }; +} + +sub disable_pagination { + my ($self) = @_; + $self->disabled(1); +} + +sub final_params { + my ($self, %params) = @_; + + my $from_form = { + page => $::form->{ $self->form_params->[0] } || 1, + per_page => $::form->{ $self->form_params->[1] } * 1, + }; + +# my $priv = _priv($self); + $params{page} = $from_form->{page} unless defined $params{page}; + $params{per_page} = $from_form->{per_page} unless defined $params{per_page}; + + $params{page} = ($params{page} * 1) || 1; + $params{per_page} = ($params{per_page} * 1) || $self->per_page; + + %params; +} + +# +# private functions +# + +sub init_form_params { + [ qw(page per_page) ] +} + +sub init_paginate_args { + '__FILTER__' +} + +sub _callback_handler_for_paginated { + my ($self, %params) = @_; + my %form_params = $self->final_params; +# my $priv = _priv($self); + + if ($self->is_enabled && $form_params{page}) { + $params{ $self->form_params->[0] } = $form_params{page}; + $params{ $self->form_params->[1] } = $form_params{per_page} if $form_params{per_page}; + } + + # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params); + + return %params; +} + +sub _get_models_handler_for_paginated { + my ($self, %params) = @_; + + $self->get_models->manager->paginate($self->final_params, args => \%params) if $self->is_enabled; + + # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params); + + return %params; +} + +sub is_enabled { + my ($self) = @_; + return !$self->disabled; +} + +1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::Controller::Helper::Paginated - A helper for semi-automatic handling +of paginating lists of database models in a controller + +=head1 SYNOPSIS + +In a controller: + + use SL::Controller::Helper::GetModels; + use SL::Controller::Helper::Paginated; + + __PACKAGE__->make_paginated( + MODEL => 'BackgroundJobHistory', + ONLY => [ qw(list) ], + FORM_PARAMS => [ qw(page per_page) ], + ); + + sub action_list { + my ($self) = @_; + + my $paginated_models = $self->get_models; + $self->render('controller/list', ENTRIES => $paginated_models); + } + +In said template: + + [% USE L %] + + + + + ... + + + + + [% FOREACH entry = ENTRIES %] + + ... + + [% END %] + +
+ + [% L.paginate_controls %] + +=head1 OVERVIEW + +This specialized helper module enables controllers to display a +paginatable list of database models with as few lines as possible. It +can also be combined trivially with the L +helper for sortable lists. + +For this to work the controller has to provide the information which +indexes are eligible for paginateing etc. by a call to +L at compile time. + +The underlying functionality that enables the use of more than just +the paginate helper is provided by the controller helper +C. See the documentation for L for +more information on it. + +A template can use the method C from the layout +helper module C which renders the links for navigation between the +pages. + +This module requires that the Rose model managers use their C +helper. + +The C helper hooks into the controller call to the action via +a C hook. This is done so that it can remember the paginate +parameters that were used in the current view. + +=head1 PACKAGE FUNCTIONS + +=over 4 + +=item C + +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. + +The hash C<%paginate_spec> can include the following parameters: + +=over 4 + +=item * C + +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 the C would default to +C). + +=item * C + +Optional. Either a code reference or the name of function to be called +on the controller importing this helper. + +If this funciton is given then the paginate helper calls it whenever +it has to count the total number of models for calculating the number +of pages to display. The function must return a hash reference with +elements suitable for passing to a Rose model manager's C +function. + +This can be used e.g. when filtering is used. + +=item * C + +Optional. An integer: the number of models to return per page. + +Defaults to the underlying database model's default number of models +per page. + +=item * C + +Optional. An array reference with exactly two strings that name the +indexes in C<$::form> in which the current page's number (the first +element in the array) and the number of models per page (the second +element in the array) are stored. + +Defaults to the values C and C if missing. + +=item * C + +Optional. An array reference containing a list of action names for +which the paginate parameters should be saved. If missing or empty then +all actions invoked on the controller are monitored. + +=back + +=back + +=head1 INSTANCE FUNCTIONS + +These functions are called on a controller instance. + +=over 4 + +=item C + +Returns a hash containing the currently active paginate +parameters. The following keys are returned: + +=over 4 + +=item * C + +The currently active page number (numbering starts at 1). + +=item * C + +Number of models per page (at least 1). + +=item * C + +Number of pages to display (at least 1). + +=item * C + +An array reference with one hash reference for each possible +page. Each hash ref contains the keys C (C<1> if that page is +the currently active page), C (the page number this hash +reference describes) and C (whether or not it should be +displayed). + +=back + +=item C + +Returns a hash reference to the paginate spec structure given in the call +to L after normalization (hash reference construction, +applying default parameters etc). + +=item C + +Disable pagination for the duration of the current action. Can be used +when using the attribute C to L does not +cover all cases. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus Em.bunkus@linet-services.deE + +=cut diff --git a/SL/Controller/Helper/GetModels/Sorted.pm b/SL/Controller/Helper/GetModels/Sorted.pm new file mode 100644 index 000000000..9e133b8f6 --- /dev/null +++ b/SL/Controller/Helper/GetModels/Sorted.pm @@ -0,0 +1,367 @@ +package SL::Controller::Helper::GetModels::Sorted; + +use strict; +use parent 'SL::Controller::Helper::GetModels::Base'; + +use Carp; +use List::MoreUtils qw(uniq); + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(by dir specs) ], + 'scalar --get_set_init' => [ qw(form_params) ], +); + +sub init { + my ($self, %specs) = @_; + + $self->set_get_models(delete $specs{get_models}); + my %model_sort_spec = $self->get_models->manager->_sort_spec; + + if (my $default = delete $specs{_default}) { + $self->by ($default->{by}); + $self->dir($default->{dir}); + } else { + $self->by ($model_sort_spec{default}[0]); + $self->dir($model_sort_spec{default}[1]); + } + + while (my ($column, $spec) = each %specs) { + next if $column =~ m/^[A-Z_]+$/; + + $spec = $specs{$column} = { title => $spec } if (ref($spec) || '') ne 'HASH'; + + $spec->{model} ||= $self->get_models->model; + $spec->{model_column} ||= $column; + } + $self->specs(\%specs); + + $self->get_models->register_handlers( + callback => sub { shift; $self->_callback_handler_for_sorted(@_) }, + get_models => sub { shift; $self->_get_models_handler_for_sorted(@_) }, + ); + +# $::lxdebug->dump(0, "CONSPEC", \%specs); +} + +sub get_current_sort_params { + my ($self, %params) = @_; + + my %sort_params; + my ($by, $dir) = @{ $self->form_params }; + + if ($::form->{ $by }) { + %sort_params = ( + sort_by => $::form->{$by}, + sort_dir => defined($::form->{$dir}) ? $::form->{$dir} * 1 : undef, + ); + } elsif (!$self->by) { + %sort_params = %params; + } else { + %sort_params = ( + sort_by => $self->by, + sort_dir => $self->dir, + ); + } + + return %sort_params; +} + +sub set_report_generator_sort_options { + my ($self, %params) = @_; + + $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns); + + my %current_sort_params = $self->get_current_sort_params; + + foreach my $col (@{ $params{sortable_columns} }) { + $params{report}->{columns}->{$col}->{link} = $self->get_models->get_callback( + sort_by => $col, + sort_dir => ($current_sort_params{sort_by} eq $col ? 1 - $current_sort_params{sort_dir} : $current_sort_params{sort_dir}), + ); + } + + $params{report}->set_sort_indicator($current_sort_params{sort_by}, 1 - $current_sort_params{sort_dir}); + + if ($params{report}->{export}) { + $params{report}->{export}->{variable_list} = [ uniq( + @{ $params{report}->{export}->{variable_list} }, + @{ $self->form_params } + )]; + } +} + +# +# private functions +# + +sub _callback_handler_for_sorted { + my ($self, %params) = @_; + my %spec = $self->get_current_sort_params; + + if ($spec{sort_by}) { + $params{ $self->form_params->[0] } = $spec{sort_by}; + $params{ $self->form_params->[1] } = $spec{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->specs->{ $sort_params{sort_by} }; + + $params{sort_by} = "SL::DB::Manager::$sort_spec->{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{sort_dir}); + + # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params); + + return %params; +} + + +sub init_form_params { + [ qw(sort_by sort_dir) ] +} + +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_models; + $self->render('controller/list', ENTRIES => $sorted_models); + } + +In said template: + + [% USE L %] + + + + + + + + + [% FOREACH entry = ENTRIES %] + + + + + + [% END %] +
[% L.sortable_table_header('package_name') %][% L.sortable_table_header('run_at') %][% L.sortable_table_header('error') %]
[% HTML.escape(entry.package_name) %][% HTML.escape(entry.run_at) %][% HTML.escape(entry.error) %]
+ +=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 at +compile time. + +The underlying functionality that enables the use of more than just +the sort helper is provided by the controller helper C. It +provides mechanisms for helpers like this one to hook into certain +calls made by the controller (C and C) so +that the specialized helpers can inject their parameters into the +calls to e.g. C. + +A template on the other hand can use the method +C from the layout helper module C. + +This module requires that the Rose model managers use their C +helper. + +The C helper hooks into the controller call to the action via +a C 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 + +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: + +=over 4 + +=item * C + +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 the C would default to +C). + +=item * C + +Optional. 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). + +Defaults to the underlying database model's default sort column name. + +=item * C + +Optional. Default sort direction (ascending for trueish values, +descrending for falsish values). + +Defaults to the underlying database model's default sort direction. + +=item * C + +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 and C if missing. + +=item * C + +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 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. + +Note that this string must be the untranslated English version of the +string. The titles will be translated whenever they're requested. + +=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). + +=item C<set_report_generator_sort_options %params> + +This function does three things with an instance of +L<SL::ReportGenerator>: + +=over 4 + +=item 1. it sets the sort indicator, + +=item 2. it sets the the links for those column headers that are +sortable and + +=item 3. it adds the C<FORM_PARAMS> fields to the list of variables in +the report generator's export options. + +=back + +The report generator instance must be passed as the parameter +C<report>. The parameter C<sortable_columns> must be an array +reference of column names that are sortable. + +The report generator instance must already have its columns and export +options set via calls to its L<SL::ReportGenerator::set_columns> and +L<SL::ReportGenerator::set_export_options> functions. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt> + +=cut diff --git a/SL/Controller/Helper/Paginated.pm b/SL/Controller/Helper/Paginated.pm deleted file mode 100644 index 0db285f1e..000000000 --- a/SL/Controller/Helper/Paginated.pm +++ /dev/null @@ -1,335 +0,0 @@ -package SL::Controller::Helper::Paginated; - -use strict; - -use Exporter qw(import); -our @EXPORT = qw(make_paginated get_paginate_spec get_current_paginate_params _save_current_paginate_params _get_models_handler_for_paginated _callback_handler_for_paginated disable_pagination); - -use constant PRIV => '__paginatedhelper_priv'; - -use List::Util qw(min); - -my %controller_paginate_spec; - -sub make_paginated { - my ($class, %specs) = @_; - - $specs{MODEL} ||= $class->controller_name; - $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x; - $specs{PER_PAGE} ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page; - $specs{FORM_PARAMS} ||= [ qw(page per_page) ]; - $specs{PAGINATE_ARGS} ||= '__FILTER__'; - $specs{ONLY} ||= []; - $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY}; - $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 }; - - $controller_paginate_spec{$class} = \%specs; - - my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : (); - $class->run_before('_save_current_paginate_params', %hook_params); - - SL::Controller::Helper::GetModels::register_get_models_handlers( - $class, - callback => '_callback_handler_for_paginated', - get_models => '_get_models_handler_for_paginated', - ONLY => $specs{ONLY}, - ); - - # $::lxdebug->dump(0, "CONSPEC", \%specs); -} - -sub get_paginate_spec { - my ($class_or_self) = @_; - - return $controller_paginate_spec{ref($class_or_self) || $class_or_self}; -} - -sub get_current_paginate_params { - my ($self, %params) = @_; - - my $spec = $self->get_paginate_spec; - - my $priv = _priv($self); - $params{page} = $priv->{page} unless defined $params{page}; - $params{per_page} = $priv->{per_page} unless defined $params{per_page}; - - my %paginate_params = ( - page => ($params{page} * 1) || 1, - per_page => ($params{per_page} * 1) || $spec->{PER_PAGE}, - ); - - # try to use Filtered if available and nothing else is configured, but don't - # blow up if the controller does not use Filtered - my %paginate_args = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) } - : $spec->{PAGINATE_ARGS} eq '__FILTER__' - && $self->can('get_current_filter_params') ? $self->get_current_filter_params - : $spec->{PAGINATE_ARGS} ne '__FILTER__' ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } } - : (); - my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args); - - # $::lxdebug->dump(0, "get_current_paginate_params: ", $calculated_params); - - return %{ $calculated_params }; -} - -sub disable_pagination { - my ($self) = @_; - _priv($self)->{disabled} = 1; -} - -# -# private functions -# - -sub _save_current_paginate_params { - my ($self) = @_; - - return if !_is_enabled($self); - - my $paginate_spec = $self->get_paginate_spec; - $self->{PRIV()} = { - page => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1, - per_page => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1, - }; - - # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page}); -} - -sub _callback_handler_for_paginated { - my ($self, %params) = @_; - my $priv = _priv($self); - - if (_is_enabled($self) && $priv->{page}) { - my $paginate_spec = $self->get_paginate_spec; - $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page}; - $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page}; - } - - # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params); - - return %params; -} - -sub _get_models_handler_for_paginated { - my ($self, %params) = @_; - my $spec = $self->get_paginate_spec; - $params{model} ||= $spec->{MODEL}; - - "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self); - - # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params); - - return %params; -} - -sub _priv { - my ($self) = @_; - $self->{PRIV()} ||= {}; - return $self->{PRIV()}; -} - -sub _is_enabled { - my ($self) = @_; - return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'}); -} - -1; -__END__ - -=pod - -=encoding utf8 - -=head1 NAME - -SL::Controller::Helper::Paginated - A helper for semi-automatic handling -of paginating lists of database models in a controller - -=head1 SYNOPSIS - -In a controller: - - use SL::Controller::Helper::GetModels; - use SL::Controller::Helper::Paginated; - - __PACKAGE__->make_paginated( - MODEL => 'BackgroundJobHistory', - ONLY => [ qw(list) ], - FORM_PARAMS => [ qw(page per_page) ], - ); - - sub action_list { - my ($self) = @_; - - my $paginated_models = $self->get_models; - $self->render('controller/list', ENTRIES => $paginated_models); - } - -In said template: - - [% USE L %] - - <table> - <thead> - <tr> - ... - </tr> - </thead> - - <tbody> - [% FOREACH entry = ENTRIES %] - <tr> - ... - </tr> - [% END %] - </tbody> - </table> - - [% L.paginate_controls %] - -=head1 OVERVIEW - -This specialized helper module enables controllers to display a -paginatable list of database models with as few lines as possible. It -can also be combined trivially with the L<SL::Controller::Sorted> -helper for sortable lists. - -For this to work the controller has to provide the information which -indexes are eligible for paginateing etc. by a call to -L<make_paginated> at compile time. - -The underlying functionality that enables the use of more than just -the paginate helper is provided by the controller helper -C<GetModels>. See the documentation for L<SL::Controller::Sorted> for -more information on it. - -A template can use the method C<paginate_controls> from the layout -helper module C<L> which renders the links for navigation between the -pages. - -This module requires that the Rose model managers use their C<Paginated> -helper. - -The C<Paginated> helper hooks into the controller call to the action via -a C<run_before> hook. This is done so that it can remember the paginate -parameters that were used in the current view. - -=head1 PACKAGE FUNCTIONS - -=over 4 - -=item C<make_paginated %paginate_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. - -The hash C<%paginate_spec> can include the following parameters: - -=over 4 - -=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<PAGINATE_ARGS> - -Optional. Either a code reference or the name of function to be called -on the controller importing this helper. - -If this funciton is given then the paginate helper calls it whenever -it has to count the total number of models for calculating the number -of pages to display. The function must return a hash reference with -elements suitable for passing to a Rose model manager's C<get_all> -function. - -This can be used e.g. when filtering is used. - -=item * C<PER_PAGE> - -Optional. An integer: the number of models to return per page. - -Defaults to the underlying database model's default number of models -per page. - -=item * C<FORM_PARAMS> - -Optional. An array reference with exactly two strings that name the -indexes in C<$::form> in which the current page's number (the first -element in the array) and the number of models per page (the second -element in the array) are stored. - -Defaults to the values C<page> and C<per_page> if missing. - -=item * C<ONLY> - -Optional. An array reference containing a list of action names for -which the paginate parameters should be saved. If missing or empty then -all actions invoked on the controller are monitored. - -=back - -=back - -=head1 INSTANCE FUNCTIONS - -These functions are called on a controller instance. - -=over 4 - -=item C<get_paginate_spec> - -Returns a hash containing the currently active paginate -parameters. The following keys are returned: - -=over 4 - -=item * C<page> - -The currently active page number (numbering starts at 1). - -=item * C<per_page> - -Number of models per page (at least 1). - -=item * C<num_pages> - -Number of pages to display (at least 1). - -=item * C<common_pages> - -An array reference with one hash reference for each possible -page. Each hash ref contains the keys C<active> (C<1> if that page is -the currently active page), C<page> (the page number this hash -reference describes) and C<visible> (whether or not it should be -displayed). - -=back - -=item C<get_current_paginate_params> - -Returns a hash reference to the paginate spec structure given in the call -to L<make_paginated> after normalization (hash reference construction, -applying default parameters etc). - -=item C<disable_pagination> - -Disable pagination for the duration of the current action. Can be used -when using the attribute C<ONLY> to L<make_paginated> does not -cover all cases. - -=back - -=head1 BUGS - -Nothing here yet. - -=head1 AUTHOR - -Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt> - -=cut diff --git a/SL/Controller/Helper/Sorted.pm b/SL/Controller/Helper/Sorted.pm deleted file mode 100644 index 2bdc51b6b..000000000 --- a/SL/Controller/Helper/Sorted.pm +++ /dev/null @@ -1,387 +0,0 @@ -package SL::Controller::Helper::Sorted; - -use strict; - -use Carp; -use List::MoreUtils qw(uniq); - -use Exporter qw(import); -our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params set_report_generator_sort_options - _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted); - -use constant PRIV => '__sortedhelperpriv'; - -my %controller_sort_spec; - -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) || '') ne 'HASH'; - - $spec->{model} ||= $specs{MODEL}; - $spec->{model_column} ||= $column; - } - - my %model_sort_spec = "SL::DB::Manager::$specs{MODEL}"->_sort_spec; - $specs{DEFAULT_DIR} = $specs{DEFAULT_DIR} ? 1 : defined($specs{DEFAULT_DIR}) ? $specs{DEFAULT_DIR} * 1 : $model_sort_spec{default}->[1]; - $specs{DEFAULT_BY} ||= $model_sort_spec{default}->[0]; - $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ]; - $specs{ONLY} ||= []; - $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY}; - - $controller_sort_spec{$class} = \%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{ref($class_or_self) || $class_or_self}; -} - -sub get_current_sort_params { - my ($self, %params) = @_; - - my $sort_spec = $self->get_sort_spec; - - if (!$params{sort_by}) { - my $priv = $self->{PRIV()} || {}; - $params{sort_by} = $priv->{by}; - $params{sort_dir} = $priv->{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; -} - -sub set_report_generator_sort_options { - my ($self, %params) = @_; - - $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns); - - my %current_sort_params = $self->get_current_sort_params; - - foreach my $col (@{ $params{sortable_columns} }) { - $params{report}->{columns}->{$col}->{link} = $self->get_callback( - sort_by => $col, - sort_dir => ($current_sort_params{by} eq $col ? 1 - $current_sort_params{dir} : $current_sort_params{dir}), - ); - } - - $params{report}->set_sort_indicator($current_sort_params{by}, 1 - $current_sort_params{dir}); - - if ($params{report}->{export}) { - $params{report}->{export}->{variable_list} = [ uniq( - @{ $params{report}->{export}->{variable_list} }, - @{ $self->get_sort_spec->{FORM_PARAMS} } - )]; - } -} - -# -# private functions -# - -sub _save_current_sort_params { - my ($self) = @_; - - my $sort_spec = $self->get_sort_spec; - my $dir_idx = $sort_spec->{FORM_PARAMS}->[1]; - $self->{PRIV()} = { - by => $::form->{ $sort_spec->{FORM_PARAMS}->[0] }, - dir => defined($::form->{$dir_idx}) ? $::form->{$dir_idx} * 1 : undef, - }; - - # $::lxdebug->message(0, "saving current sort params to " . $self->{PRIV()}->{by} . ' / ' . $self->{PRIV()}->{dir}); -} - -sub _callback_handler_for_sorted { - my ($self, %params) = @_; - - my $priv = $self->{PRIV()} || {}; - if ($priv->{by}) { - my $sort_spec = $self->get_sort_spec; - $params{ $sort_spec->{FORM_PARAMS}->[0] } = $priv->{by}; - $params{ $sort_spec->{FORM_PARAMS}->[1] } = $priv->{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_models; - $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>. - -This module requires that the Rose model managers use their C<Sorted> -helper. - -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: - -=over 4 - -=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<DEFAULT_BY> - -Optional. 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). - -Defaults to the underlying database model's default sort column name. - -=item * C<DEFAULT_DIR> - -Optional. Default sort direction (ascending for trueish values, -descrending for falsish values). - -Defaults to the underlying database model's default sort direction. - -=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. - -Note that this string must be the untranslated English version of the -string. The titles will be translated whenever they're requested. - -=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). - -=item C<set_report_generator_sort_options %params> - -This function does three things with an instance of -L<SL::ReportGenerator>: - -=over 4 - -=item 1. it sets the sort indicator, - -=item 2. it sets the the links for those column headers that are -sortable and - -=item 3. it adds the C<FORM_PARAMS> fields to the list of variables in -the report generator's export options. - -=back - -The report generator instance must be passed as the parameter -C<report>. The parameter C<sortable_columns> must be an array -reference of column names that are sortable. - -The report generator instance must already have its columns and export -options set via calls to its L<SL::ReportGenerator::set_columns> and -L<SL::ReportGenerator::set_export_options> functions. - -=back - -=head1 BUGS - -Nothing here yet. - -=head1 AUTHOR - -Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt> - -=cut diff --git a/SL/Template/Plugin/L.pm b/SL/Template/Plugin/L.pm index ef534ce04..7c5357303 100644 --- a/SL/Template/Plugin/L.pm +++ b/SL/Template/Plugin/L.pm @@ -465,17 +465,19 @@ sub paginate_controls { my ($self, %params) = _hashify(1, @_); my $controller = $self->{CONTEXT}->stash->get('SELF'); - my $paginate_spec = $controller->get_paginate_spec; - my %paginate_params = $controller->get_current_paginate_params; + my $pager = $params{models}->paginated; +# my $paginate_spec = $controller->get_paginate_spec; + + my %paginate_params = $params{models}->get_paginate_args; my %template_params = ( pages => \%paginate_params, url_maker => sub { my %url_params = _hashify(0, @_); - $url_params{ $paginate_spec->{FORM_PARAMS}->[0] } = delete $url_params{page}; - $url_params{ $paginate_spec->{FORM_PARAMS}->[1] } = delete $url_params{per_page} if exists $url_params{per_page}; + $url_params{ $pager->form_params->[0] } = delete $url_params{page}; + $url_params{ $pager->form_params->[1] } = delete $url_params{per_page} if exists $url_params{per_page}; - return $controller->get_callback(%url_params); + return $params{models}->get_callback(%url_params); }, %params, ); diff --git a/templates/webpages/delivery_plan/report_bottom.html b/templates/webpages/delivery_plan/report_bottom.html index 79e152304..da08e0758 100644 --- a/templates/webpages/delivery_plan/report_bottom.html +++ b/templates/webpages/delivery_plan/report_bottom.html @@ -1,2 +1,2 @@ [% USE L %] -[%- L.paginate_controls %] +[%- L.paginate_controls(models=models) %]