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