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