Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 9deadd1d

Von Moritz Bunkus vor etwa 12 Jahren hinzugefügt

  • ID 9deadd1d1a37ce24b807132c00f4890785400683
  • Vorgänger cf32b30b
  • Nachfolger 35f3e56a

Controller-Helfer für das halbautomatische Sortieren von Listenansichten

Unterschiede anzeigen:

SL/Controller/Helper/GetModels.pm
1
package SL::Controller::Helper::GetModels;
2

  
3
use strict;
4

  
5
use Exporter qw(import);
6
our @EXPORT = qw(get_callback get_models);
7

  
8
my $current_action;
9
my %registered_handlers = ( callback => [], get_models => [] );
10

  
11
sub register_get_models_handlers {
12
  my ($class, %additional_handlers) = @_;
13

  
14
  my $only        = delete($additional_handlers{ONLY}) || [];
15
  $only           = [ $only ] if !ref $only;
16
  my %hook_params = @{ $only } ? ( only => $only ) : ();
17

  
18
  $class->run_before(sub { $current_action = $_[1]; }, %hook_params);
19

  
20
  map { push @{ $registered_handlers{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %registered_handlers;
21
}
22

  
23
sub get_callback {
24
  my ($self, %override_params) = @_;
25

  
26
  my %default_params = _run_handlers($self, 'callback', action => $current_action);
27

  
28
  return $self->url_for(%default_params, %override_params);
29
}
30

  
31
sub get_models {
32
  my ($self, %override_params) = @_;
33

  
34
  my %default_params           = _run_handlers($self, 'get_models');
35

  
36
  my %params                   = (%default_params, %override_params);
37
  my $model                    = delete($params{model}) || die "No 'model' to work on";
38

  
39
  return "SL::DB::Manager::${model}"->get_all(%params);
40
}
41

  
42
#
43
# private/internal functions
44
#
45

  
46
sub _run_handlers {
47
  my ($self, $handler_type, %params) = @_;
48

  
49
  foreach my $sub (@{ $registered_handlers{$handler_type} }) {
50
    if (ref $sub eq 'CODE') {
51
      %params = $sub->($self, %params);
52
    } elsif ($self->can($sub)) {
53
      %params = $self->$sub(%params);
54
    } else {
55
      die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
56
    }
57
  }
58

  
59
  return %params;
60
}
61

  
62
1;
63
__END__
64

  
65
=pod
66

  
67
=encoding utf8
68

  
69
=head1 NAME
70

  
71
SL::Controller::Helper::GetModels - Base mixin for controller helpers
72
dealing with semi-automatic handling of sorting and paginating lists
73

  
74
=head1 SYNOPSIS
75

  
76
For a proper synopsis see L<SL::Controller::Helper::Sorted>.
77

  
78
=head1 OVERVIEW
79

  
80
For a generic overview see L<SL::Controller::Helper::Sorted>.
81

  
82
This base module is the interface between a controller and specialized
83
helper modules that handle things like sorting and paginating. The
84
specialized helpers register themselves with this module via a call to
85
L<register_get_models_handlers> during compilation time (e.g. in the
86
case of C<Sorted> this happens when the controller calls
87
L<SL::Controller::Helper::Sorted::make_sorted>).
88

  
89
A controller will later usually call the L<get_models>
90
function. Templates will call the L<get_callback> function. Both
91
functions run the registered handlers handing over control to the
92
specialized helpers so that they may inject their parameters into the
93
call chain.
94

  
95
The C<GetModels> helper hooks into the controller call to the action
96
via a C<run_before> hook. This is done so that it can remember the
97
action called by the user. This is used for constructing the callback
98
in L<get_callback>.
99

  
100
=head1 PACKAGE FUNCTIONS
101

  
102
=over 4
103

  
104
=item C<register_get_models_handlers $class, %handlers>
105

  
106
This function should only be called from other controller helpers like
107
C<Sorted> or C<Paginated>. It is not exported and must therefore be
108
called its full name. The first parameter C<$class> must be the actual
109
controller's class name.
110

  
111
If C<%handlers> contains a key C<ONLY> then it is passed to the hook
112
registration in L<SL::Controller::Base::run_before>.
113

  
114
The C<%handlers> register callback functions in the specialized
115
controller helpers that are called during invocation of
116
L<get_callback> or L<get_models>. Possible keys are C<callback> and
117
C<models>.
118

  
119
Each handler (the value in the hash) can be either a code reference
120
(in which case it is called directly) or the name of an instance
121
function callable on a controller instance. In both cases the handler
122
receives a hash of parameters built during this very call to
123
L<get_callback> or L<get_models> respectively. The handler's return
124
value must be the new hash to be used in calls to further handlers and
125
to the actual database model functions later on.
126

  
127
=back
128

  
129
=head1 INSTANCE FUNCTIONS
130

  
131
=over 4
132

  
133
=item C<get_callback [%params]>
134

  
135
Return an URL suitable for use as a callback parameter. It maps to the
136
current controller and action. All registered handlers of type
137
'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
138
the parameters they need so that the same list view as is currently
139
visible can be re-rendered.
140

  
141
Optional C<%params> passed to this function may override any parameter
142
set by the registered handlers.
143

  
144
=item C<get_models [%params]>
145

  
146
Query the model manager via C<get_all> and return its result. The
147
parameters to C<get_all> are constructed by calling all registered
148
handlers of type 'models' (e.g. the ones by C<Sorted> and
149
C<Paginated>).
150

  
151
Optional C<%params> passed to this function may override any parameter
152
set by the registered handlers.
153

  
154
The return value is the an array reference of C<Rose> models.
155

  
156
=back
157

  
158
=head1 BUGS
159

  
160
Nothing here yet.
161

  
162
=head1 AUTHOR
163

  
164
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
165

  
166
=cut
SL/Controller/Helper/Sorted.pm
1
package SL::Controller::Helper::Sorted;
2

  
3
use strict;
4

  
5
use Exporter qw(import);
6
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);
7

  
8
my ($controller_sort_spec, $current_sort_by, $current_sort_dir);
9

  
10
sub make_sorted {
11
  my ($class, %specs) = @_;
12

  
13
  $specs{MODEL} ||=  $class->_controller_name;
14
  $specs{MODEL}   =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
15

  
16
  while (my ($column, $spec) = each %specs) {
17
    next if $column =~ m/^[A-Z_]+$/;
18

  
19
    $spec = $specs{$column} = { title => $spec } if !ref $spec;
20

  
21
    $spec->{model}        ||= $specs{MODEL};
22
    $spec->{model_column} ||= $column;
23
  }
24

  
25
  $specs{DEFAULT_DIR}   = $specs{DEFAULT_DIR} || !defined($specs{DEFAULT_DIR}) ? 1 : 0;
26
  $specs{DEFAULT_BY}  ||= "SL::DB::$specs{MODEL}::Manager"->_get_sort_spec($class)->{default}->[0];
27
  $specs{FORM_PARAMS} ||= [ qw(sort_by sort_dir) ];
28
  $specs{ONLY}        ||= [];
29
  $specs{ONLY}          = [ $specs{ONLY} ] if !ref $specs{ONLY};
30

  
31
  $controller_sort_spec = \%specs;
32

  
33
  my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
34
  $class->run_before('_save_current_sort_params', %hook_params);
35

  
36
  SL::Controller::Helper::GetModels::register_get_models_handlers(
37
    $class,
38
    callback   => '_callback_handler_for_sorted',
39
    get_models => '_get_models_handler_for_sorted',
40
    ONLY       => $specs{ONLY},
41
  );
42

  
43
  # $::lxdebug->dump(0, "CONSPEC", \%specs);
44
}
45

  
46
sub get_sort_spec {
47
  my ($class_or_self) = @_;
48

  
49
  return $controller_sort_spec;
50
}
51

  
52
sub get_current_sort_params {
53
  my ($self, %params) = @_;
54

  
55
  my $sort_spec = $self->get_sort_spec;
56

  
57
  if (!$params{sort_by}) {
58
    $params{sort_by}  = $current_sort_by;
59
    $params{sort_dir} = $current_sort_dir;
60
  }
61

  
62
  my $by          = $params{sort_by} || $sort_spec->{DEFAULT_BY};
63
  my %sort_params = (
64
    dir => defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{DEFAULT_DIR},
65
    by  => $sort_spec->{$by} ? $by : $sort_spec->{DEFAULT_BY},
66
  );
67

  
68
  return %sort_params;
69
}
70

  
71
#
72
# private functions
73
#
74

  
75
sub _save_current_sort_params {
76
  my ($self) = @_;
77

  
78
  my $sort_spec     = $self->get_sort_spec;
79
  $current_sort_by  =   $::form->{ $sort_spec->{FORM_PARAMS}->[0] };
80
  $current_sort_dir = !!$::form->{ $sort_spec->{FORM_PARAMS}->[1] } * 1;
81

  
82
  # $::lxdebug->message(0, "saving current sort params to $current_sort_by / $current_sort_dir");
83
}
84

  
85
sub _callback_handler_for_sorted {
86
  my ($self, %params) = @_;
87

  
88
  if ($current_sort_by) {
89
    my $sort_spec                             = $self->get_sort_spec;
90
    $params{ $sort_spec->{FORM_PARAMS}->[0] } = $current_sort_by;
91
    $params{ $sort_spec->{FORM_PARAMS}->[1] } = $current_sort_dir;
92
  }
93

  
94
  # $::lxdebug->dump(0, "CB handler for sorted; params nach modif:", \%params);
95

  
96
  return %params;
97
}
98

  
99
sub _get_models_handler_for_sorted {
100
  my ($self, %params) = @_;
101

  
102
  my %sort_params     = $self->get_current_sort_params;
103
  my $sort_spec       = $self->get_sort_spec->{ $sort_params{by} };
104

  
105
  $params{model}      = $sort_spec->{model};
106
  $params{sort_by}    = "SL::DB::Manager::$params{model}"->make_sort_string(sort_by => $sort_spec->{model_column}, sort_dir => $sort_params{dir});
107

  
108
  # $::lxdebug->dump(0, "GM handler for sorted; params nach modif:", \%params);
109

  
110
  return %params;
111
}
112

  
113
1;
114
__END__
115

  
116
=pod
117

  
118
=encoding utf8
119

  
120
=head1 NAME
121

  
122
SL::Controller::Helper::Sorted - A helper for semi-automatic handling
123
of sorting lists of database models in a controller
124

  
125
=head1 SYNOPSIS
126

  
127
In a controller:
128

  
129
  use SL::Controller::Helper::GetModels;
130
  use SL::Controller::Helper::Sorted;
131

  
132
  __PACKAGE__->make_sorted(
133
    DEFAULT_BY   => 'run_at',
134
    DEFAULT_DIR  => 1,
135
    MODEL        => 'BackgroundJobHistory',
136
    ONLY         => [ qw(list) ],
137

  
138
    error        => $::locale->text('Error'),
139
    package_name => $::locale->text('Package name'),
140
    run_at       => $::locale->text('Run at'),
141
  );
142

  
143
  sub action_list {
144
    my ($self) = @_;
145

  
146
    my $sorted_models = $self->get_sorted;
147
    $self->render('controller/list', ENTRIES => $sorted_models);
148
  }
149

  
150
In said template:
151

  
152
  [% USE L %]
153

  
154
  <table>
155
   <tr>
156
    <th>[% L.sortable_table_header('package_name') %]</th>
157
    <th>[% L.sortable_table_header('run_at') %]</th>
158
    <th>[% L.sortable_table_header('error') %]</th>
159
   </tr>
160

  
161
   [% FOREACH entry = ENTRIES %]
162
    <tr>
163
     <td>[% HTML.escape(entry.package_name) %]</td>
164
     <td>[% HTML.escape(entry.run_at) %]</td>
165
     <td>[% HTML.escape(entry.error) %]</td>
166
    </tr>
167
   [% END %]
168
  </table>
169

  
170
=head1 OVERVIEW
171

  
172
This specialized helper module enables controllers to display a
173
sortable list of database models with as few lines as possible.
174

  
175
For this to work the controller has to provide the information which
176
indexes are eligible for sorting etc. by a call to L<make_sorted> at
177
compile time.
178

  
179
The underlying functionality that enables the use of more than just
180
the sort helper is provided by the controller helper C<GetModels>. It
181
provides mechanisms for helpers like this one to hook into certain
182
calls made by the controller (C<get_callback> and C<get_models>) so
183
that the specialized helpers can inject their parameters into the
184
calls to e.g. C<SL::DB::Manager::SomeModel::get_all>.
185

  
186
A template on the other hand can use the method
187
C<sortable_table_header> from the layout helper module C<L>.
188

  
189
The C<Sorted> helper hooks into the controller call to the action via
190
a C<run_before> hook. This is done so that it can remember the sort
191
parameters that were used in the current view.
192

  
193
=head1 PACKAGE FUNCTIONS
194

  
195
=over 4
196

  
197
=item C<make_sorted %sort_spec>
198

  
199
This function must be called by a controller at compile time. It is
200
uesd to set the various parameters required for this helper to do its
201
magic.
202

  
203
There are two sorts of keys in the hash C<%sort_spec>. The first kind
204
is written in all upper-case. Those parameters are control
205
parameters. The second kind are all lower-case and represent indexes
206
that can be used for sorting (similar to database column names). The
207
second kind are also the indexes you use in a template when calling
208
C<[% L.sorted_table_header(...) %]>.
209

  
210
Control parameters include the following (all required parameters
211
occur first):
212

  
213
=over 4
214

  
215
=item * C<DEFAULT_BY>
216

  
217
Required. A string: the index to sort by if the user hasn't clicked on
218
any column yet (meaning: if the C<$::form> parameters for sorting do
219
not contain a valid index).
220

  
221
=item * C<DEFAULT_DIR>
222

  
223
Optional. Default sort direction (ascending for trueish values,
224
descrending for falsish values).
225

  
226
Defaults to C<1> if missing.
227

  
228
=item * C<MODEL>
229

  
230
Optional. A string: the name of the Rose database model that is used
231
as a default in certain cases. If this parameter is missing then it is
232
derived from the controller's package (e.g. for the controller
233
C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
234
C<BackgroundJobHistory>).
235

  
236
=item * C<FORM_PARAMS>
237

  
238
Optional. An array reference with exactly two strings that name the
239
indexes in C<$::form> in which the sort index (the first element in
240
the array) and sort direction (the second element in the array) are
241
stored.
242

  
243
Defaults to the values C<sort_by> and C<sort_dir> if missing.
244

  
245
=item * C<ONLY>
246

  
247
Optional. An array reference containing a list of action names for
248
which the sort parameters should be saved. If missing or empty then
249
all actions invoked on the controller are monitored.
250

  
251
=back
252

  
253
All keys that are written in all lower-case name indexes that can be
254
used for sorting. Each value to such a key can be either a string or a
255
hash reference containing certain elements. If the value is only a
256
string then such a hash reference is constructed, and the string is
257
used as the value for the C<title> key.
258

  
259
These possible elements are:
260

  
261
=over 4
262

  
263
=item * C<title>
264

  
265
Required. A user-displayable title to be used by functions like the
266
layout helper's C<sortable_table_header>. Does not have a default
267
value.
268

  
269
=item * C<model>
270

  
271
Optional. The name of a Rose database model this sort index refers
272
to. If missing then the value of C<$sort_spec{MODEL}> is used.
273

  
274
=item * C<model_column>
275

  
276
Optional. The name of the Rose database model column this sort index
277
refers to. It must be one of the columns named by the model's
278
C<Sorted> helper (not to be confused with the controller's C<Sorted>
279
helper!).
280

  
281
If missing it defaults to the key in C<%sort_spec> for which this hash
282
reference is the value.
283

  
284
=back
285

  
286
=back
287

  
288
=head1 INSTANCE FUNCTIONS
289

  
290
These functions are called on a controller instance.
291

  
292
=over 4
293

  
294
=item C<get_sort_spec>
295

  
296
Returns a hash containing the currently active sort parameters.
297

  
298
The key C<by> contains the active sort index referring to the
299
C<%sort_spec> given to L<make_sorted>.
300

  
301
The key C<dir> is either C<1> or C<0>.
302

  
303
=item C<get_current_sort_params>
304

  
305
Returns a hash reference to the sort spec structure given in the call
306
to L<make_sorted> after normalization (hash reference construction,
307
applying default parameters etc).
308

  
309
=back
310

  
311
=head1 BUGS
312

  
313
Nothing here yet.
314

  
315
=head1 AUTHOR
316

  
317
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
318

  
319
=cut
SL/Template/Plugin/L.pm
582 582
  return substr($text, 0, $params{at}) . '...';
583 583
}
584 584

  
585
sub sortable_table_header {
586
  my ($self, $by, @slurp) = @_;
587
  my %params              = _hashify(@slurp);
588

  
589
  my $controller          = $self->{CONTEXT}->stash->get('SELF');
590
  my $sort_spec           = $controller->get_sort_spec;
591
  my $by_spec             = $sort_spec->{$by};
592
  my %current_sort_params = $controller->get_current_sort_params;
593
  my ($image, $new_dir)   = ('', $current_sort_params{dir});
594
  my $title               = delete($params{title}) || $by_spec->{title};
595

  
596
  if ($current_sort_params{by} eq $by) {
597
    my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
598
    $image          = '<img border="0" src="image/' . $current_dir . '.png">';
599
    $new_dir        = 1 - ($current_sort_params{dir} || 0);
600
  }
601

  
602
  $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
603
  $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
604

  
605
  return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
606
}
607

  
585 608
1;
586 609

  
587 610
__END__
......
864 887

  
865 888
Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
866 889

  
890
=item C<sortable_table_header $by, %params>
891

  
892
Create a link and image suitable for placement in a table
893
header. C<$by> must be an index set up by the controller with
894
L<SL::Controller::Helper::make_sorted>.
895

  
896
The optional parameter C<$params{title}> can override the column title
897
displayed to the user. Otherwise the column title from the
898
controller's sort spec is used.
899

  
900
The other parameters in C<%params> are passed unmodified to the
901
underlying call to L<SL::Controller::Base::url_for>.
902

  
903
See the documentation of L<SL::Controller::Helper::Sorted> for an
904
overview and further usage instructions.
905

  
867 906
=back
868 907

  
869 908
=head2 CONVERSION FUNCTIONS

Auch abrufbar als: Unified diff