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