Datumvalidierung bei ENTER, d.h bei onchange, nich bei onblur.
[kivitendo-erp.git] / SL / Template / Plugin / L.pm
1 package SL::Template::Plugin::L;
2
3 use base qw( Template::Plugin );
4 use Template::Plugin;
5 use Data::Dumper;
6 use List::MoreUtils qw(apply);
7 use List::Util qw(max);
8 use Scalar::Util qw(blessed);
9
10 use SL::Presenter;
11 use SL::Util qw(_hashify);
12
13 use strict;
14
15 { # This will give you an id for identifying html tags and such.
16   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
17   # Do not use these id's to store information across requests.
18 my $_id_sequence = int rand 1e7;
19 sub _tag_id {
20   return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
21 }
22 }
23
24 sub _H {
25   my $string = shift;
26   return $::locale->quote_special_chars('HTML', $string);
27 }
28
29 sub _J {
30   my $string = shift;
31   $string    =~ s/(\"|\'|\\)/\\$1/g;
32   return $string;
33 }
34
35 sub new {
36   my ($class, $context, @args) = @_;
37
38   return bless {
39     CONTEXT => $context,
40   }, $class;
41 }
42
43 sub _context {
44   die 'not an accessor' if @_ > 1;
45   return $_[0]->{CONTEXT};
46 }
47
48 sub _call_presenter {
49   my ($method, $self, @args) = @_;
50
51   my $presenter              = $::request->presenter;
52
53   if (!$presenter->can($method)) {
54     $::lxdebug->message(LXDebug::WARN(), "SL::Presenter has no method named '$method'!");
55     return '';
56   }
57
58   splice @args, -1, 1, %{ $args[-1] } if @args && (ref($args[-1]) eq 'HASH');
59
60   $presenter->$method(@args);
61 }
62
63 sub name_to_id    { return _call_presenter('name_to_id',    @_); }
64 sub html_tag      { return _call_presenter('html_tag',      @_); }
65 sub hidden_tag    { return _call_presenter('hidden_tag',    @_); }
66 sub select_tag    { return _call_presenter('select_tag',    @_); }
67 sub checkbox_tag  { return _call_presenter('checkbox_tag',  @_); }
68 sub input_tag     { return _call_presenter('input_tag',     @_); }
69 sub javascript    { return _call_presenter('javascript',    @_); }
70 sub truncate      { return _call_presenter('truncate',      @_); }
71 sub simple_format { return _call_presenter('simple_format', @_); }
72 sub part_picker   { return _call_presenter('part_picker',   @_); }
73 sub chart_picker  { return _call_presenter('chart_picker',  @_); }
74 sub customer_vendor_picker   { return _call_presenter('customer_vendor_picker',   @_); }
75 sub project_picker           { return _call_presenter('project_picker',           @_); }
76 sub button_tag               { return _call_presenter('button_tag',               @_); }
77 sub submit_tag               { return _call_presenter('submit_tag',               @_); }
78 sub ajax_submit_tag          { return _call_presenter('ajax_submit_tag',          @_); }
79
80 sub _set_id_attribute {
81   my ($attributes, $name, $unique) = @_;
82   SL::Presenter::Tag::_set_id_attribute($attributes, $name, $unique);
83 }
84
85 sub img_tag {
86   my ($self, %options) = _hashify(1, @_);
87
88   $options{alt} ||= '';
89
90   return $self->html_tag('img', undef, %options);
91 }
92
93 sub textarea_tag {
94   my ($self, $name, $content, %attributes) = _hashify(3, @_);
95
96   _set_id_attribute(\%attributes, $name);
97   $attributes{rows}  *= 1; # required by standard
98   $attributes{cols}  *= 1; # required by standard
99   $content            = $content ? _H($content) : '';
100
101   return $self->html_tag('textarea', $content, %attributes, name => $name);
102 }
103
104 sub radio_button_tag {
105   my ($self, $name, %attributes) = _hashify(2, @_);
106
107   $attributes{value}   = 1 unless exists $attributes{value};
108
109   _set_id_attribute(\%attributes, $name, 1);
110   my $label            = delete $attributes{label};
111
112   _set_id_attribute(\%attributes, $name . '_' . $attributes{value});
113
114   if ($attributes{checked}) {
115     $attributes{checked} = 'checked';
116   } else {
117     delete $attributes{checked};
118   }
119
120   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
121   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
122
123   return $code;
124 }
125
126 sub div_tag {
127   my ($self, $content, @slurp) = @_;
128   return $self->html_tag('div', $content, @slurp);
129 }
130
131 sub ul_tag {
132   my ($self, $content, @slurp) = @_;
133   return $self->html_tag('ul', $content, @slurp);
134 }
135
136 sub li_tag {
137   my ($self, $content, @slurp) = @_;
138   return $self->html_tag('li', $content, @slurp);
139 }
140
141 sub link {
142   my ($self, $href, $content, %params) = _hashify(3, @_);
143
144   $href ||= '#';
145
146   return $self->html_tag('a', $content, %params, href => $href);
147 }
148
149 sub yes_no_tag {
150   my ($self, $name, $value, %attributes) = _hashify(3, @_);
151
152   return $self->select_tag($name, [ [ 1 => $::locale->text('Yes') ], [ 0 => $::locale->text('No') ] ], default => $value ? 1 : 0, %attributes);
153 }
154
155 sub stylesheet_tag {
156   my $self = shift;
157   my $code = '';
158
159   foreach my $file (@_) {
160     $file .= '.css'        unless $file =~ m/\.css$/;
161     $file  = "css/${file}" unless $file =~ m|/|;
162
163     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
164   }
165
166   return $code;
167 }
168
169 my $date_tag_id_idx = 0;
170 sub date_tag {
171   my ($self, $name, $value, %params) = _hashify(3, @_);
172
173   _set_id_attribute(\%params, $name);
174   my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
175   my @classes  = $params{no_cal} || $params{readonly} ? () : ('datepicker');
176   push @classes, delete($params{class}) if $params{class};
177   my %class    = @classes ? (class => join(' ', @classes)) : ();
178
179   $::request->presenter->need_reinit_widgets($params{id});
180
181   return $self->input_tag(
182     $name, blessed($value) ? $value->to_lxoffice : $value,
183     size   => 11,
184     onchange => "check_right_date_format(this);",
185     %params,
186     %class, @onchange,
187   );
188 }
189
190 # simple version with select_tag
191 sub vendor_selector {
192   my ($self, $name, $value, %params) = _hashify(3, @_);
193
194   my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
195                          (ref $value && $value->can('id')) ? $value->id : '';
196
197   return $self->select_tag($name, SL::DB::Manager::Vendor->get_all(),
198                                   default      => $actual_vendor_id,
199                                   title_sub    => sub { $_[0]->vendornumber . " : " . $_[0]->name },
200                                   'with_empty' => 1,
201                                   %params);
202 }
203
204
205 # simple version with select_tag
206 sub part_selector {
207   my ($self, $name, $value, %params) = _hashify(3, @_);
208
209   my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
210                        (ref $value && $value->can('id')) ? $value->id : '';
211
212   return $self->select_tag($name, SL::DB::Manager::Part->get_all(),
213                            default      => $actual_part_id,
214                            title_sub    => sub { $_[0]->partnumber . " : " . $_[0]->description },
215                            with_empty   => 1,
216                            %params);
217 }
218
219
220 sub javascript_tag {
221   my $self = shift;
222   my $code = '';
223
224   foreach my $file (@_) {
225     $file .= '.js'        unless $file =~ m/\.js$/;
226     $file  = "js/${file}" unless $file =~ m|/|;
227
228     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
229   }
230
231   return $code;
232 }
233
234 sub tabbed {
235   my ($self, $tabs, %params) = _hashify(2, @_);
236   my $id       = $params{id} || 'tab_' . _tag_id();
237
238   $params{selected} *= 1;
239
240   die 'L.tabbed needs an arrayred of tabs for first argument'
241     unless ref $tabs eq 'ARRAY';
242
243   my (@header, @blocks);
244   for my $i (0..$#$tabs) {
245     my $tab = $tabs->[$i];
246
247     next if $tab eq '';
248
249     my $tab_id = "__tab_id_$i";
250     push @header, $self->li_tag($self->link('#' . $tab_id, $tab->{name}));
251     push @blocks, $self->div_tag($tab->{data}, id => $tab_id);
252   }
253
254   return '' unless @header;
255
256   my $ul = $self->ul_tag(join('', @header), id => $id);
257   return $self->div_tag(join('', $ul, @blocks), class => 'tabwidget');
258 }
259
260 sub tab {
261   my ($self, $name, $src, %params) = _hashify(3, @_);
262
263   $params{method} ||= 'process';
264
265   return () if defined $params{if} && !$params{if};
266
267   my $data;
268   if ($params{method} eq 'raw') {
269     $data = $src;
270   } elsif ($params{method} eq 'process') {
271     $data = $self->_context->process($src, %{ $params{args} || {} });
272   } else {
273     die "unknown tag method '$params{method}'";
274   }
275
276   return () unless $data;
277
278   return +{ name => $name, data => $data };
279 }
280
281 sub areainput_tag {
282   my ($self, $name, $value, %attributes) = _hashify(3, @_);
283
284   my $cols    = delete $attributes{cols} || delete $attributes{size};
285   my $minrows = delete $attributes{min_rows} || 1;
286   my $maxrows = delete $attributes{max_rows};
287   my $rows    = $::form->numtextrows($value, $cols, $maxrows, $minrows);
288
289   return $rows > 1
290     ? $self->textarea_tag($name, $value, %attributes, rows => $rows, cols => $cols)
291     : $self->input_tag($name, $value, %attributes, size => $cols);
292 }
293
294 sub multiselect2side {
295   my ($self, $id, %params) = _hashify(2, @_);
296
297   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
298   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
299   $params{moveOptions}    = 'false';
300
301   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
302   my $code                = <<EOCODE;
303 <script type="text/javascript">
304   \$().ready(function() {
305     \$('#${id}').multiselect2side({ ${vars} });
306   });
307 </script>
308 EOCODE
309
310   return $code;
311 }
312
313 sub sortable_element {
314   my ($self, $selector, %params) = _hashify(2, @_);
315
316   my %attributes = ( distance => 5,
317                      helper   => <<'JAVASCRIPT' );
318     function(event, ui) {
319       ui.children().each(function() {
320         $(this).width($(this).width());
321       });
322       return ui;
323     }
324 JAVASCRIPT
325
326   my $stop_event = '';
327
328   if ($params{url} && $params{with}) {
329     my $as      = $params{as} || $params{with};
330     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
331     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
332
333     my $params_js = $params{params} ? qq| + ($params{params})| : '';
334
335     $stop_event = <<JAVASCRIPT;
336         \$.post('$params{url}'${params_js}, { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
337 JAVASCRIPT
338   }
339
340   if (!$params{dont_recolor}) {
341     $stop_event .= <<JAVASCRIPT;
342         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
343         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
344 JAVASCRIPT
345   }
346
347   if ($stop_event) {
348     $attributes{stop} = <<JAVASCRIPT;
349       function(event, ui) {
350         ${stop_event}
351         return ui;
352       }
353 JAVASCRIPT
354   }
355
356   $params{handle}     = '.dragdrop' unless exists $params{handle};
357   $attributes{handle} = "'$params{handle}'" if $params{handle};
358
359   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
360
361   my $code = <<JAVASCRIPT;
362 <script type="text/javascript">
363   \$(function() {
364     \$( "${selector}" ).sortable({ ${attr_str} })
365   });
366 </script>
367 JAVASCRIPT
368
369   return $code;
370 }
371
372 sub dump {
373   my $self = shift;
374   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
375 }
376
377 sub sortable_table_header {
378   my ($self, $by, %params) = _hashify(2, @_);
379
380   my $controller          = $self->{CONTEXT}->stash->get('SELF');
381   my $models              = $params{models} || $self->{CONTEXT}->stash->get('MODELS');
382   my $sort_spec           = $models->get_sort_spec;
383   my $by_spec             = $sort_spec->{$by};
384   my %current_sort_params = $models->get_current_sort_params;
385   my ($image, $new_dir)   = ('', $current_sort_params{dir});
386   my $title               = delete($params{title}) || $::locale->text($by_spec->{title});
387
388   if ($current_sort_params{sort_by} eq $by) {
389     my $current_dir = $current_sort_params{sort_dir} ? 'up' : 'down';
390     $image          = '<img border="0" src="image/' . $current_dir . '.png">';
391     $new_dir        = 1 - ($current_sort_params{sort_dir} || 0);
392   }
393
394   $params{ $models->sorted->form_params->[0] } = $by;
395   $params{ $models->sorted->form_params->[1] } = ($new_dir ? '1' : '0');
396
397   return '<a href="' . $models->get_callback(%params) . '">' . _H($title) . $image . '</a>';
398 }
399
400 sub paginate_controls {
401   my ($self, %params) = _hashify(1, @_);
402
403   my $controller      = $self->{CONTEXT}->stash->get('SELF');
404   my $models          = $params{models} || $self->{CONTEXT}->stash->get('MODELS');
405   my $pager           = $models->paginated;
406 #  my $paginate_spec   = $controller->get_paginate_spec;
407
408   my %paginate_params = $models->get_paginate_args;
409
410   my %template_params = (
411     pages             => \%paginate_params,
412     url_maker         => sub {
413       my %url_params                                    = _hashify(0, @_);
414       $url_params{ $pager->form_params->[0] } = delete $url_params{page};
415       $url_params{ $pager->form_params->[1] } = delete $url_params{per_page} if exists $url_params{per_page};
416
417       return $models->get_callback(%url_params);
418     },
419     %params,
420   );
421
422   return SL::Presenter->get->render('common/paginate', %template_params);
423 }
424
425 1;
426
427 __END__
428
429 =head1 NAME
430
431 SL::Templates::Plugin::L -- Layouting / tag generation
432
433 =head1 SYNOPSIS
434
435 Usage from a template:
436
437   [% USE L %]
438
439   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
440
441   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
442                                  { direction => 'right', display => 'To the right' } ],
443                                value_key => 'direction', title_key => 'display', default => 'right')) %]
444
445   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
446                                  { direction => 'right', display => 'To the right', selected => 1 } ],
447                                value_key => 'direction', title_key => 'display')) %]
448
449 =head1 DESCRIPTION
450
451 A module modeled a bit after Rails' ActionView helpers. Several small
452 functions that create HTML tags from various kinds of data sources.
453
454 The C<id> attribute is usually calculated automatically. This can be
455 overridden by either specifying an C<id> attribute or by setting
456 C<no_id> to trueish.
457
458 =head1 FUNCTIONS
459
460 =head2 LOW-LEVEL FUNCTIONS
461
462 The following items are just forwarded to L<SL::Presenter::Tag>:
463
464 =over 2
465
466 =item * C<name_to_id $name>
467
468 =item * C<stringify_attributes %items>
469
470 =item * C<html_tag $tag_name, $content_string, %attributes>
471
472 =back
473
474 =head2 HIGH-LEVEL FUNCTIONS
475
476 The following functions are just forwarded to L<SL::Presenter::Tag>:
477
478 =over 2
479
480 =item * C<input_tag $name, $value, %attributes>
481
482 =item * C<select_tag $name, \@collection, %attributes>
483
484 =back
485
486 Available high-level functions implemented in this module:
487
488 =over 4
489
490 =item C<yes_no_tag $name, $value, %attributes>
491
492 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
493 calling L<select_tag>. C<$value> determines
494 which entry is selected. The C<%attributes> are passed through to
495 L<select_tag>.
496
497 =item C<hidden_tag $name, $value, %attributes>
498
499 Creates a HTML 'input type=hidden' tag named C<$name> with the value
500 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
501 tag's C<id> defaults to C<name_to_id($name)>.
502
503 =item C<textarea_tag $name, $value, %attributes>
504
505 Creates a HTML 'textarea' tag named C<$name> with the content
506 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
507 tag's C<id> defaults to C<name_to_id($name)>.
508
509 =item C<checkbox_tag $name, %attributes>
510
511 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
512 HTML attributes from C<%attributes>. The tag's C<id> defaults to
513 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
514
515 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
516 created with said C<label>. No attribute named C<label> is created in
517 that case.
518
519 If C<%attributes> contains a key C<checkall> then the value is taken as a
520 JQuery selector and clicking this checkbox will also toggle all checkboxes
521 matching the selector.
522
523 =item C<date_tag $name, $value, %attributes>
524
525 Creates a date input field, with an attached javascript that will open a
526 calendar on click.
527
528 =item C<radio_button_tag $name, %attributes>
529
530 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
531 HTML attributes from C<%attributes>. The tag's C<value> defaults to
532 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
533
534 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
535 created with said C<label>. No attribute named C<label> is created in
536 that case.
537
538 =item C<javascript_tag $file1, $file2, $file3...>
539
540 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
541 tag for each file name parameter passed. Each file name will be
542 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
543 doesn't contain a slash.
544
545 =item C<stylesheet_tag $file1, $file2, $file3...>
546
547 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
548 for each file name parameter passed. Each file name will be postfixed
549 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
550 contain a slash.
551
552 =item C<tabbed \@tab, %attributes>
553
554 Will create a tabbed area. The tabs should be created with the helper function
555 C<tab>. Example:
556
557   [% L.tabbed([
558     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
559     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
560   ]) %]
561
562 =item C<areainput_tag $name, $content, %PARAMS>
563
564 Creates a generic input tag or textarea tag, depending on content size. The
565 amount of desired rows must be either given with the C<rows> parameter or can
566 be computed from the value and the C<cols> paramter, Accepted parameters
567 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
568
569 You can force input by setting rows to 1, and you can force textarea by setting
570 rows to anything >1.
571
572 =item C<multiselect2side $id, %params>
573
574 Creates a JavaScript snippet calling the jQuery function
575 C<multiselect2side> on the select control with the ID C<$id>. The
576 select itself is not created. C<%params> can contain the following
577 entries:
578
579 =over 2
580
581 =item C<labelsx>
582
583 The label of the list of available options. Defaults to the
584 translation of 'Available'.
585
586 =item C<labeldx>
587
588 The label of the list of selected options. Defaults to the
589 translation of 'Selected'.
590
591 =back
592
593 =item C<sortable_element $selector, %params>
594
595 Makes the children of the DOM element C<$selector> (a jQuery selector)
596 sortable with the I<jQuery UI Selectable> library. The children can be
597 dragged & dropped around. After dropping an element an URL can be
598 postet to with the element IDs of the sorted children.
599
600 If this is used then the JavaScript file C<js/jquery-ui.js> must be
601 included manually as well as it isn't loaded via C<$::form-gt;header>.
602
603 C<%params> can contain the following entries:
604
605 =over 2
606
607 =item C<url>
608
609 The URL to POST an AJAX request to after a dragged element has been
610 dropped. The AJAX request's return value is ignored. If given then
611 C<$params{with}> must be given as well.
612
613 =item C<with>
614
615 A string that is interpreted as the prefix of the children's ID. Upon
616 POSTing the result each child whose ID starts with C<$params{with}> is
617 considered. The prefix and the following "_" is removed from the
618 ID. The remaining parts of the IDs of those children are posted as a
619 single array parameter. The array parameter's name is either
620 C<$params{as}> or, missing that, C<$params{with}>.
621
622 =item C<as>
623
624 Sets the POST parameter name for AJAX request after dropping an
625 element (see C<$params{with}>).
626
627 =item C<handle>
628
629 An optional jQuery selector specifying which part of the child element
630 is dragable. If the parameter is not given then it defaults to
631 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
632 parameter is set and empty then the whole child element is dragable,
633 and clicks through to underlying elements like inputs or links might
634 not work.
635
636 =item C<dont_recolor>
637
638 If trueish then the children will not be recolored. The default is to
639 recolor the children by setting the class C<listrow0> on odd and
640 C<listrow1> on even entries.
641
642 =item C<params>
643
644 An optional JavaScript string that is evaluated before sending the
645 POST request. The result must be a string that is appended to the URL.
646
647 =back
648
649 Example:
650
651   <script type="text/javascript" src="js/jquery-ui.js"></script>
652
653   <table id="thing_list">
654     <thead>
655       <tr><td>This</td><td>That</td></tr>
656     </thead>
657     <tbody>
658       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
659       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
660       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
661     </tbody>
662   <table>
663
664   [% L.sortable_element('#thing_list tbody',
665                         url          => 'controller.pl?action=SystemThings/reorder',
666                         with         => 'thingy',
667                         as           => 'thing_ids',
668                         recolor_rows => 1) %]
669
670 After dropping e.g. the third element at the top of the list a POST
671 request would be made to the C<reorder> action of the C<SystemThings>
672 controller with a single parameter called C<thing_ids> -- an array
673 containing the values C<[ 6, 2, 15 ]>.
674
675 =item C<dump REF>
676
677 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
678
679 =item C<sortable_table_header $by, %params>
680
681 Create a link and image suitable for placement in a table
682 header. C<$by> must be an index set up by the controller with
683 L<SL::Controller::Helper::make_sorted>.
684
685 The optional parameter C<$params{title}> can override the column title
686 displayed to the user. Otherwise the column title from the
687 controller's sort spec is used.
688
689 The other parameters in C<%params> are passed unmodified to the
690 underlying call to L<SL::Controller::Base::url_for>.
691
692 See the documentation of L<SL::Controller::Helper::Sorted> for an
693 overview and further usage instructions.
694
695 =item C<paginate_controls>
696
697 Create a set of links used to paginate a list view.
698
699 See the documentation of L<SL::Controller::Helper::Paginated> for an
700 overview and further usage instructions.
701
702 =back
703
704 =head2 CONVERSION FUNCTIONS
705
706 =over 4
707
708 =item C<tab, description, target, %PARAMS>
709
710 Creates a tab for C<tabbed>. The description will be used as displayed name.
711 The target should be a block or template that can be processed. C<tab> supports
712 a C<method> parameter, which can override the process method to apply target.
713 C<method => 'raw'> will just include the given text as is. I was too lazy to
714 implement C<include> properly.
715
716 Also an C<if> attribute is supported, so that tabs can be suppressed based on
717 some occasion. In this case the supplied block won't even get processed, and
718 the resulting tab will get ignored by C<tabbed>:
719
720   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
721
722 =item C<truncate $text, [%params]>
723
724 See L<SL::Presenter::Text/truncate>.
725
726 =item C<simple_format $text>
727
728 See L<SL::Presenter::Text/simple_format>.
729
730 =back
731
732 =head1 MODULE AUTHORS
733
734 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
735
736 L<http://linet-services.de>