b78ebb01543715b4ef34e87353efb1e0c7b1e09d
[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 List::MoreUtils qw(apply);
6 use List::Util qw(max);
7
8 use strict;
9
10 { # This will give you an id for identifying html tags and such.
11   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
12   # Do not use these id's to store information across requests.
13 my $_id_sequence = int rand 1e7;
14 sub _tag_id {
15   return $_id_sequence = ($_id_sequence + 1) % 1e7;
16 }
17 }
18
19 sub _H {
20   my $string = shift;
21   return $::locale->quote_special_chars('HTML', $string);
22 }
23
24 sub _J {
25   my $string =  "" . shift;
26   $string    =~ s/\"/\\\"/g;
27   return $string;
28 }
29
30 sub _hashify {
31   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
32 }
33
34 sub new {
35   my ($class, $context, @args) = @_;
36
37   return bless {
38     CONTEXT => $context,
39   }, $class;
40 }
41
42 sub _context {
43   die 'not an accessor' if @_ > 1;
44   return $_[0]->{CONTEXT};
45 }
46
47 sub name_to_id {
48   my $self =  shift;
49   my $name =  shift;
50
51   $name    =~ s/[^\w_]/_/g;
52   $name    =~ s/_+/_/g;
53
54   return $name;
55 }
56
57 sub attributes {
58   my ($self, @slurp)    = @_;
59   my %options = _hashify(@slurp);
60
61   my @result = ();
62   while (my ($name, $value) = each %options) {
63     next unless $name;
64     next if $name eq 'disabled' && !$value;
65     $value = '' if !defined($value);
66     push @result, _H($name) . '="' . _H($value) . '"';
67   }
68
69   return @result ? ' ' . join(' ', @result) : '';
70 }
71
72 sub html_tag {
73   my ($self, $tag, $content, @slurp) = @_;
74   my $attributes = $self->attributes(@slurp);
75
76   return "<${tag}${attributes}/>" unless defined($content);
77   return "<${tag}${attributes}>${content}</${tag}>";
78 }
79
80 sub select_tag {
81   my $self            = shift;
82   my $name            = shift;
83   my $options_str     = shift;
84   my %attributes      = _hashify(@_);
85
86   $attributes{id}   ||= $self->name_to_id($name);
87   $options_str        = $self->options_for_select($options_str) if ref $options_str;
88
89   return $self->html_tag('select', $options_str, %attributes, name => $name);
90 }
91
92 sub textarea_tag {
93   my ($self, $name, $content, @slurp) = @_;
94   my %attributes      = _hashify(@slurp);
95
96   $attributes{id}   ||= $self->name_to_id($name);
97   $content            = $content ? _H($content) : '';
98
99   return $self->html_tag('textarea', $content, %attributes, name => $name);
100 }
101
102 sub checkbox_tag {
103   my ($self, $name, @slurp) = @_;
104   my %attributes       = _hashify(@slurp);
105
106   $attributes{id}    ||= $self->name_to_id($name);
107   $attributes{value}   = 1 unless defined $attributes{value};
108   my $label            = delete $attributes{label};
109
110   if ($attributes{checked}) {
111     $attributes{checked} = 'checked';
112   } else {
113     delete $attributes{checked};
114   }
115
116   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
117   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
118
119   return $code;
120 }
121
122 sub radio_button_tag {
123   my $self             = shift;
124   my $name             = shift;
125   my %attributes       = _hashify(@_);
126
127   $attributes{value}   = 1 unless defined $attributes{value};
128   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
129   my $label            = delete $attributes{label};
130
131   if ($attributes{checked}) {
132     $attributes{checked} = 'checked';
133   } else {
134     delete $attributes{checked};
135   }
136
137   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
138   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
139
140   return $code;
141 }
142
143 sub input_tag {
144   my ($self, $name, $value, @slurp) = @_;
145   my %attributes      = _hashify(@slurp);
146
147   $attributes{id}   ||= $self->name_to_id($name);
148   $attributes{type} ||= 'text';
149
150   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
151 }
152
153 sub hidden_tag {
154   return shift->input_tag(@_, type => 'hidden');
155 }
156
157 sub div_tag {
158   my ($self, $content, @slurp) = @_;
159   return $self->html_tag('div', $content, @slurp);
160 }
161
162 sub ul_tag {
163   my ($self, $content, @slurp) = @_;
164   return $self->html_tag('ul', $content, @slurp);
165 }
166
167 sub li_tag {
168   my ($self, $content, @slurp) = @_;
169   return $self->html_tag('li', $content, @slurp);
170 }
171
172 sub link {
173   my ($self, $href, $content, @slurp) = @_;
174   my %params = _hashify(@slurp);
175
176   $href ||= '#';
177
178   return $self->html_tag('a', $content, %params, href => $href);
179 }
180
181 sub submit_tag {
182   my ($self, $name, $value, @slurp) = @_;
183   my %attributes = _hashify(@slurp);
184
185   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
186
187   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
188 }
189
190 sub button_tag {
191   my ($self, $onclick, $value, @slurp) = @_;
192   my %attributes = _hashify(@slurp);
193
194   return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
195 }
196
197 sub options_for_select {
198   my $self            = shift;
199   my $collection      = shift;
200   my %options         = _hashify(@_);
201
202   my $value_key       = $options{value} || 'id';
203   my $title_key       = $options{title} || $value_key;
204
205   my $value_sub       = $options{value_sub};
206   my $title_sub       = $options{title_sub};
207
208   my $value_title_sub = $options{value_title_sub};
209
210   my %selected        = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
211
212   my $access = sub {
213     my ($element, $index, $key, $sub) = @_;
214     my $ref = ref $element;
215     return  $sub            ? $sub->($element)
216          : !$ref            ? $element
217          :  $ref eq 'ARRAY' ? $element->[$index]
218          :  $ref eq 'HASH'  ? $element->{$key}
219          :                    $element->$key;
220   };
221
222   my @elements = ();
223   push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
224   push @elements, map [
225     $value_title_sub ? $value_title_sub->($_) : (
226       $access->($_, 0, $value_key, $value_sub),
227       $access->($_, 1, $title_key, $title_sub),
228     )
229   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
230
231   my $code = '';
232   foreach my $result (@elements) {
233     my %attributes = ( value => $result->[0] );
234     $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
235
236     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
237   }
238
239   return $code;
240 }
241
242 sub javascript {
243   my ($self, $data) = @_;
244   return $self->html_tag('script', $data, type => 'text/javascript');
245 }
246
247 sub stylesheet_tag {
248   my $self = shift;
249   my $code = '';
250
251   foreach my $file (@_) {
252     $file .= '.css'        unless $file =~ m/\.css$/;
253     $file  = "css/${file}" unless $file =~ m|/|;
254
255     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
256   }
257
258   return $code;
259 }
260
261 sub date_tag {
262   my ($self, $name, $value, @slurp) = @_;
263   my %params   = _hashify(@slurp);
264   my $name_e   = _H($name);
265   my $seq      = _tag_id();
266   my $datefmt  = apply {
267     s/d+/\%d/gi;
268     s/m+/\%m/gi;
269     s/y+/\%Y/gi;
270   } $::myconfig{"dateformat"};
271
272   $params{cal_align} ||= 'BR';
273
274   $self->input_tag($name, $value,
275     id     => $name_e,
276     size   => 11,
277     title  => _H($::myconfig{dateformat}),
278     onBlur => 'check_right_date_format(this)',
279     %params,
280   ) . ((!$params{no_cal}) ?
281   $self->html_tag('img', undef,
282     src    => 'image/calendar.png',
283     id     => "trigger$seq",
284     title  => _H($::myconfig{dateformat}),
285     %params,
286   ) .
287   $self->javascript(
288     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
289   ) : '');
290 }
291
292 sub javascript_tag {
293   my $self = shift;
294   my $code = '';
295
296   foreach my $file (@_) {
297     $file .= '.js'        unless $file =~ m/\.js$/;
298     $file  = "js/${file}" unless $file =~ m|/|;
299
300     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
301   }
302
303   return $code;
304 }
305
306 sub tabbed {
307   my ($self, $tabs, @slurp) = @_;
308   my %params   = _hashify(@slurp);
309   my $id       = $params{id} || 'tab_' . _tag_id();
310
311   $params{selected} *= 1;
312
313   die 'L.tabbed needs an arrayred of tabs for first argument'
314     unless ref $tabs eq 'ARRAY';
315
316   my (@header, @blocks);
317   for my $i (0..$#$tabs) {
318     my $tab = $tabs->[$i];
319
320     next if $tab eq '';
321
322     my $selected = $params{selected} == $i;
323     my $tab_id   = "__tab_id_$i";
324     push @header, $self->li_tag(
325       $self->link('', $tab->{name}, rel => $tab_id),
326         ($selected ? (class => 'selected') : ())
327     );
328     push @blocks, $self->div_tag($tab->{data},
329       id => $tab_id, class => 'tabcontent');
330   }
331
332   return '' unless @header;
333   return $self->ul_tag(
334     join('', @header), id => $id, class => 'shadetabs'
335   ) .
336   $self->div_tag(
337     join('', @blocks), class => 'tabcontentstyle'
338   ) .
339   $self->javascript(
340     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
341     qq|$id.setselectedClassTarget("link");$id.init();|
342   );
343 }
344
345 sub tab {
346   my ($self, $name, $src, @slurp) = @_;
347   my %params = _hashify(@slurp);
348
349   $params{method} ||= 'process';
350
351   return () if defined $params{if} && !$params{if};
352
353   my $data;
354   if ($params{method} eq 'raw') {
355     $data = $src;
356   } elsif ($params{method} eq 'process') {
357     $data = $self->_context->process($src, %{ $params{args} || {} });
358   } else {
359     die "unknown tag method '$params{method}'";
360   }
361
362   return () unless $data;
363
364   return +{ name => $name, data => $data };
365 }
366
367 sub areainput_tag {
368   my ($self, $name, $value, @slurp) = @_;
369   my %attributes      = _hashify(@slurp);
370
371   my $rows = delete $attributes{rows}     || 1;
372   my $min  = delete $attributes{min_rows} || 1;
373
374   return $rows > 1
375     ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
376     : $self->input_tag($name, $value, %attributes);
377 }
378
379 sub multiselect2side {
380   my ($self, $id, @slurp) = @_;
381   my %params              = _hashify(@slurp);
382
383   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
384   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
385   $params{moveOptions}    = 'false';
386
387   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
388   my $code                = <<EOCODE;
389 <script type="text/javascript">
390   \$().ready(function() {
391     \$('#${id}').multiselect2side({ ${vars} });
392   });
393 </script>
394 EOCODE
395
396   return $code;
397 }
398
399 sub sortable_element {
400   my ($self, $selector, @slurp) = @_;
401   my %params                    = _hashify(@slurp);
402
403   my %attributes = ( distance => 5,
404                      helper   => <<'JAVASCRIPT' );
405     function(event, ui) {
406       ui.children().each(function() {
407         $(this).width($(this).width());
408       });
409       return ui;
410     }
411 JAVASCRIPT
412
413   my $stop_event = '';
414
415   if ($params{url} && $params{with}) {
416     my $as      = $params{as} || $params{with};
417     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
418     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
419
420     $stop_event = <<JAVASCRIPT;
421         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
422 JAVASCRIPT
423   }
424
425   if (!$params{dont_recolor}) {
426     $stop_event .= <<JAVASCRIPT;
427         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
428         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
429 JAVASCRIPT
430   }
431
432   if ($stop_event) {
433     $attributes{stop} = <<JAVASCRIPT;
434       function(event, ui) {
435         ${stop_event}
436         return ui;
437       }
438 JAVASCRIPT
439   }
440
441   $params{handle}     = '.dragdrop' unless exists $params{handle};
442   $attributes{handle} = "'$params{handle}'" if $params{handle};
443
444   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
445
446   my $code = <<JAVASCRIPT;
447 <script type="text/javascript">
448   \$(function() {
449     \$( "${selector}" ).sortable({ ${attr_str} })
450   });
451 </script>
452 JAVASCRIPT
453
454   return $code;
455 }
456
457 sub online_help_tag {
458   my ($self, $tag, @slurp) = @_;
459   my %params               = _hashify(@slurp);
460   my $cc                   = $::myconfig{countrycode};
461   my $file                 = "doc/online/$cc/$tag.html";
462   my $text                 = $params{text} || $::locale->text('Help');
463
464   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
465   return unless -f $file;
466   return $self->html_tag('a', $text, href => $file, target => '_blank');
467 }
468
469 sub dump {
470   my $self = shift;
471   require Data::Dumper;
472   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
473 }
474
475 1;
476
477 __END__
478
479 =head1 NAME
480
481 SL::Templates::Plugin::L -- Layouting / tag generation
482
483 =head1 SYNOPSIS
484
485 Usage from a template:
486
487   [% USE L %]
488
489   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
490
491   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
492                                                       { direction => 'right', display => 'To the right' } ],
493                                                     value => 'direction', title => 'display', default => 'right')) %]
494
495 =head1 DESCRIPTION
496
497 A module modeled a bit after Rails' ActionView helpers. Several small
498 functions that create HTML tags from various kinds of data sources.
499
500 =head1 FUNCTIONS
501
502 =head2 LOW-LEVEL FUNCTIONS
503
504 =over 4
505
506 =item C<name_to_id $name>
507
508 Converts a name to a HTML id by replacing various characters.
509
510 =item C<attributes %items>
511
512 Creates a string from all elements in C<%items> suitable for usage as
513 HTML tag attributes. Keys and values are HTML escaped even though keys
514 must not contain non-ASCII characters for browsers to accept them.
515
516 =item C<html_tag $tag_name, $content_string, %attributes>
517
518 Creates an opening and closing HTML tag for C<$tag_name> and puts
519 C<$content_string> between the two. If C<$content_string> is undefined
520 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
521 are key/value pairs added to the opening tag.
522
523 C<$content_string> is not HTML escaped.
524
525 =back
526
527 =head2 HIGH-LEVEL FUNCTIONS
528
529 =over 4
530
531 =item C<select_tag $name, $options_string, %attributes>
532
533 Creates a HTML 'select' tag named C<$name> with the contents
534 C<$options_string> and with arbitrary HTML attributes from
535 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
536
537 The C<$options_string> is usually created by the
538 L</options_for_select> function. If C<$options_string> is an array
539 reference then it will be passed to L</options_for_select>
540 automatically.
541
542 =item C<input_tag $name, $value, %attributes>
543
544 Creates a HTML 'input type=text' tag named C<$name> with the value
545 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
546 tag's C<id> defaults to C<name_to_id($name)>.
547
548 =item C<hidden_tag $name, $value, %attributes>
549
550 Creates a HTML 'input type=hidden' tag named C<$name> with the value
551 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
552 tag's C<id> defaults to C<name_to_id($name)>.
553
554 =item C<submit_tag $name, $value, %attributes>
555
556 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
557 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
558 tag's C<id> defaults to C<name_to_id($name)>.
559
560 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
561 be added via the C<onclick> handler asking the question given with
562 C<$attributes{confirm}>. If request is only submitted if the user
563 clicks the dialog's ok/yes button.
564
565 =item C<textarea_tag $name, $value, %attributes>
566
567 Creates a HTML 'textarea' tag named C<$name> with the content
568 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
569 tag's C<id> defaults to C<name_to_id($name)>.
570
571 =item C<checkbox_tag $name, %attributes>
572
573 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
574 HTML attributes from C<%attributes>. The tag's C<id> defaults to
575 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
576
577 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
578 created with said C<label>. No attribute named C<label> is created in
579 that case.
580
581 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
582
583 Creates a date input field, with an attached javascript that will open a
584 calendar on click. The javascript ist by default anchoered at the bottom right
585 sight. This can be overridden with C<cal_align>, see Calendar documentation for
586 the details, usually you'll want a two letter abbreviation of the alignment.
587 Right + Bottom becomes C<BL>.
588
589 =item C<radio_button_tag $name, %attributes>
590
591 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
592 HTML attributes from C<%attributes>. The tag's C<value> defaults to
593 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
594
595 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
596 created with said C<label>. No attribute named C<label> is created in
597 that case.
598
599 =item C<javascript_tag $file1, $file2, $file3...>
600
601 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
602 tag for each file name parameter passed. Each file name will be
603 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
604 doesn't contain a slash.
605
606 =item C<stylesheet_tag $file1, $file2, $file3...>
607
608 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
609 for each file name parameter passed. Each file name will be postfixed
610 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
611 contain a slash.
612
613 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
614
615 Creates a date input field, with an attached javascript that will open a
616 calendar on click. The javascript ist by default anchoered at the bottom right
617 sight. This can be overridden with C<cal_align>, see Calendar documentation for
618 the details, usually you'll want a two letter abbreviation of the alignment.
619 Right + Bottom becomes C<BL>.
620
621 =item C<tabbed \@tab, %attributes>
622
623 Will create a tabbed area. The tabs should be created with the helper function
624 C<tab>. Example:
625
626   [% L.tabbed([
627     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
628     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
629   ]) %]
630
631 An optional attribute is C<selected>, which accepts the ordinal of a tab which
632 should be selected by default.
633
634 =item C<areainput_tag $name, $content, %PARAMS>
635
636 Creates a generic input tag or textarea tag, depending on content size. The
637 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
638 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
639
640 You can force input by setting rows to 1, and you can force textarea by setting
641 rows to anything >1.
642
643 =item C<multiselect2side $id, %params>
644
645 Creates a JavaScript snippet calling the jQuery function
646 C<multiselect2side> on the select control with the ID C<$id>. The
647 select itself is not created. C<%params> can contain the following
648 entries:
649
650 =over 2
651
652 =item C<labelsx>
653
654 The label of the list of available options. Defaults to the
655 translation of 'Available'.
656
657 =item C<labeldx>
658
659 The label of the list of selected options. Defaults to the
660 translation of 'Selected'.
661
662 =back
663
664 =item C<sortable_element $selector, %params>
665
666 Makes the children of the DOM element C<$selector> (a jQuery selector)
667 sortable with the I<jQuery UI Selectable> library. The children can be
668 dragged & dropped around. After dropping an element an URL can be
669 postet to with the element IDs of the sorted children.
670
671 If this is used then the JavaScript file C<js/jquery-ui.js> must be
672 included manually as well as it isn't loaded via C<$::form-gt;header>.
673
674 C<%params> can contain the following entries:
675
676 =over 2
677
678 =item C<url>
679
680 The URL to POST an AJAX request to after a dragged element has been
681 dropped. The AJAX request's return value is ignored. If given then
682 C<$params{with}> must be given as well.
683
684 =item C<with>
685
686 A string that is interpreted as the prefix of the children's ID. Upon
687 POSTing the result each child whose ID starts with C<$params{with}> is
688 considered. The prefix and the following "_" is removed from the
689 ID. The remaining parts of the IDs of those children are posted as a
690 single array parameter. The array parameter's name is either
691 C<$params{as}> or, missing that, C<$params{with}>.
692
693 =item C<as>
694
695 Sets the POST parameter name for AJAX request after dropping an
696 element (see C<$params{with}>).
697
698 =item C<handle>
699
700 An optional jQuery selector specifying which part of the child element
701 is dragable. If the parameter is not given then it defaults to
702 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
703 parameter is set and empty then the whole child element is dragable,
704 and clicks through to underlying elements like inputs or links might
705 not work.
706
707 =item C<dont_recolor>
708
709 If trueish then the children will not be recolored. The default is to
710 recolor the children by setting the class C<listrow0> on odd and
711 C<listrow1> on even entries.
712
713 =back
714
715 Example:
716
717   <script type="text/javascript" src="js/jquery-ui.js"></script>
718
719   <table id="thing_list">
720     <thead>
721       <tr><td>This</td><td>That</td></tr>
722     </thead>
723     <tbody>
724       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
725       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
726       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
727     </tbody>
728   <table>
729
730   [% L.sortable_element('#thing_list tbody',
731                         url          => 'controller.pl?action=SystemThings/reorder',
732                         with         => 'thingy',
733                         as           => 'thing_ids',
734                         recolor_rows => 1) %]
735
736 After dropping e.g. the third element at the top of the list a POST
737 request would be made to the C<reorder> action of the C<SystemThings>
738 controller with a single parameter called C<thing_ids> -- an array
739 containing the values C<[ 6, 2, 15 ]>.
740
741 =item C<dump REF>
742
743 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
744
745 =back
746
747 =head2 CONVERSION FUNCTIONS
748
749 =over 4
750
751 =item C<options_for_select \@collection, %options>
752
753 Creates a string suitable for a HTML 'select' tag consisting of one
754 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
755 to use and the title to display are extracted from the elements in
756 C<\@collection>. Each element can be one of four things:
757
758 =over 12
759
760 =item 1. An array reference with at least two elements. The first element is
761 the value, the second element is its title.
762
763 =item 2. A scalar. The scalar is both the value and the title.
764
765 =item 3. A hash reference. In this case C<%options> must contain
766 I<value> and I<title> keys that name the keys in the element to use
767 for the value and title respectively.
768
769 =item 4. A blessed reference. In this case C<%options> must contain
770 I<value> and I<title> keys that name functions called on the blessed
771 reference whose return values are used as the value and title
772 respectively.
773
774 =back
775
776 For cases 3 and 4 C<$options{value}> defaults to C<id> and
777 C<$options{title}> defaults to C<$options{value}>.
778
779 In addition to pure keys/method you can also provide coderefs as I<value_sub>
780 and/or I<title_sub>. If present, these take precedence over keys or methods,
781 and are called with the element as first argument. It must return the value or
782 title.
783
784 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
785 precedence over each individual sub. It will only be called once for each
786 element and must return a list of value and title.
787
788 If the option C<with_empty> is set then an empty element (value
789 C<undef>) will be used as the first element. The title to display for
790 this element can be set with the option C<empty_title> and defaults to
791 an empty string.
792
793 The option C<default> can be either a scalar or an array reference
794 containing the values of the options which should be set to be
795 selected.
796
797 =item C<tab, description, target, %PARAMS>
798
799 Creates a tab for C<tabbed>. The description will be used as displayed name.
800 The target should be a block or template that can be processed. C<tab> supports
801 a C<method> parameter, which can override the process method to apply target.
802 C<method => 'raw'> will just include the given text as is. I was too lazy to
803 implement C<include> properly.
804
805 Also an C<if> attribute is supported, so that tabs can be suppressed based on
806 some occasion. In this case the supplied block won't even get processed, and
807 the resulting tab will get ignored by C<tabbed>:
808
809   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
810
811 =back
812
813 =head1 MODULE AUTHORS
814
815 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
816
817 L<http://linet-services.de>