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