Revision 9deadd1d
Von Moritz Bunkus vor etwa 12 Jahren hinzugefügt
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
Controller-Helfer für das halbautomatische Sortieren von Listenansichten