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