L.select_tag: Unterstützung für Nutzung von <optgroup>s
[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 SL::Presenter;
10
11 use strict;
12
13 { # This will give you an id for identifying html tags and such.
14   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
15   # Do not use these id's to store information across requests.
16 my $_id_sequence = int rand 1e7;
17 sub _tag_id {
18   return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
19 }
20 }
21
22 my %_valueless_attributes = map { $_ => 1 } qw(
23   checked compact declare defer disabled ismap multiple noresize noshade nowrap
24   readonly selected
25 );
26
27 sub _H {
28   my $string = shift;
29   return $::locale->quote_special_chars('HTML', $string);
30 }
31
32 sub _J {
33   my $string = shift;
34   $string    =~ s/(\"|\'|\\)/\\$1/g;
35   return $string;
36 }
37
38 sub _hashify {
39   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
40 }
41
42 sub new {
43   my ($class, $context, @args) = @_;
44
45   return bless {
46     CONTEXT => $context,
47   }, $class;
48 }
49
50 sub _context {
51   die 'not an accessor' if @_ > 1;
52   return $_[0]->{CONTEXT};
53 }
54
55 sub _call_presenter {
56   my ($method, @args) = @_;
57
58   my $presenter       = $::request->presenter;
59
60   return '' unless $presenter->can($method);
61
62   splice @args, -1, 1, %{ $args[-1] } if @args && (ref($args[-1]) eq 'HASH');
63
64   $presenter->$method(@args);
65 }
66
67 sub name_to_id {
68   my $self =  shift;
69   my $name =  shift;
70
71   $name    =~ s/[^\w_]/_/g;
72   $name    =~ s/_+/_/g;
73
74   return $name;
75 }
76
77 sub attributes {
78   my ($self, @slurp)    = @_;
79   my %options = _hashify(@slurp);
80
81   my @result = ();
82   while (my ($name, $value) = each %options) {
83     next unless $name;
84     next if $_valueless_attributes{$name} && !$value;
85     $value = '' if !defined($value);
86     push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
87   }
88
89   return @result ? ' ' . join(' ', @result) : '';
90 }
91
92 sub html_tag {
93   my ($self, $tag, $content, @slurp) = @_;
94   my $attributes = $self->attributes(@slurp);
95
96   return "<${tag}${attributes}>" unless defined($content);
97   return "<${tag}${attributes}>${content}</${tag}>";
98 }
99
100 sub img_tag {
101   my ($self, @slurp) = @_;
102   my %options = _hashify(@slurp);
103
104   $options{alt} ||= '';
105
106   return $self->html_tag('img', undef, %options);
107 }
108
109 sub select_tag {
110   my $self            = shift;
111   my $name            = shift;
112   my $collection      = shift;
113   my %attributes      = _hashify(@_);
114
115   $attributes{id}   ||= $self->name_to_id($name);
116
117   my $value_key       = delete($attributes{value_key}) || 'id';
118   my $title_key       = delete($attributes{title_key}) || $value_key;
119   my $default_key     = delete($attributes{default_key}) || 'selected';
120
121
122   my $value_title_sub = delete($attributes{value_title_sub});
123
124   my $value_sub       = delete($attributes{value_sub});
125   my $title_sub       = delete($attributes{title_sub});
126   my $default_sub     = delete($attributes{default_sub});
127
128   my $with_empty      = delete($attributes{with_empty});
129   my $empty_title     = delete($attributes{empty_title});
130
131   my $with_optgroups  = delete($attributes{with_optgroups});
132
133   my %selected;
134
135   if ( ref($attributes{default}) eq 'ARRAY' ) {
136
137     foreach my $entry (@{$attributes{default}}) {
138       $selected{$entry} = 1;
139     }
140   } elsif ( defined($attributes{default}) ) {
141     $selected{$attributes{default}} = 1;
142   }
143
144   delete($attributes{default});
145
146
147   my @all_options;
148   push @all_options, [undef, $empty_title || ''] if $with_empty;
149
150   my $normalize_entry = sub {
151     my ($type, $entry, $sub, $key) = @_;
152
153     return $sub->($entry) if $sub;
154
155     my $ref = ref($entry);
156
157     if ( !$ref ) {
158       return $entry if $type eq 'value' || $type eq 'title';
159       return 0;
160     }
161
162     if ( $ref eq 'ARRAY' ) {
163       return $entry->[ $type eq 'value' ? 0 : $type eq 'title' ? 1 : 2 ];
164     }
165
166     return $entry->{$key} if $ref  eq 'HASH';
167     return $entry->$key   if $type ne 'default' || $entry->can($key);
168     return undef;
169   };
170
171   my $list_to_code = sub {
172     my ($sub_collection) = @_;
173
174     my @options;
175     foreach my $entry ( @{ $sub_collection } ) {
176       my $value;
177       my $title;
178
179       if ( $value_title_sub ) {
180         ($value, $title) = @{ $value_title_sub->($entry) };
181       } else {
182
183         $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
184         $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
185       }
186
187       my $default = $normalize_entry->('default', $entry, $default_sub, $default_key);
188
189       push(@options, [$value, $title, $default]);
190     }
191
192     foreach my $entry (@options) {
193       $entry->[2] = 1 if $selected{$entry->[0]};
194     }
195
196     return join '', map { $self->html_tag('option', _H($_->[1]), value => $_->[0], selected => $_->[2]) } @options;
197   };
198
199   my $code;
200
201   if (!$with_optgroups) {
202     $code = $list_to_code->($collection);
203
204   } else {
205     $code = join '', map {
206       my ($optgroup_title, $sub_collection) = @{ $_ };
207       $self->html_tag('optgroup', $list_to_code->($sub_collection), label => $optgroup_title)
208     } @{ $collection };
209   }
210
211   return $self->html_tag('select', $code, %attributes, name => $name);
212 }
213
214 sub textarea_tag {
215   my ($self, $name, $content, @slurp) = @_;
216   my %attributes      = _hashify(@slurp);
217
218   $attributes{id}   ||= $self->name_to_id($name);
219   $attributes{rows}  *= 1; # required by standard
220   $attributes{cols}  *= 1; # required by standard
221   $content            = $content ? _H($content) : '';
222
223   return $self->html_tag('textarea', $content, %attributes, name => $name);
224 }
225
226 sub checkbox_tag {
227   my ($self, $name, @slurp) = @_;
228   my %attributes       = _hashify(@slurp);
229
230   $attributes{id}    ||= $self->name_to_id($name);
231   $attributes{value}   = 1 unless defined $attributes{value};
232   my $label            = delete $attributes{label};
233   my $checkall         = delete $attributes{checkall};
234
235   if ($attributes{checked}) {
236     $attributes{checked} = 'checked';
237   } else {
238     delete $attributes{checked};
239   }
240
241   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
242   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
243   $code    .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
244
245   return $code;
246 }
247
248 sub radio_button_tag {
249   my $self             = shift;
250   my $name             = shift;
251   my %attributes       = _hashify(@_);
252
253   $attributes{value}   = 1 unless defined $attributes{value};
254   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
255   my $label            = delete $attributes{label};
256
257   if ($attributes{checked}) {
258     $attributes{checked} = 'checked';
259   } else {
260     delete $attributes{checked};
261   }
262
263   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
264   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
265
266   return $code;
267 }
268
269 sub input_tag {
270   my ($self, $name, $value, @slurp) = @_;
271   my %attributes      = _hashify(@slurp);
272
273   $attributes{id}   ||= $self->name_to_id($name);
274   $attributes{type} ||= 'text';
275
276   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
277 }
278
279 sub hidden_tag {
280   my ($self, $name, $value, @slurp) = @_;
281   return $self->input_tag($name, $value, _hashify(@slurp), type => 'hidden');
282 }
283
284 sub div_tag {
285   my ($self, $content, @slurp) = @_;
286   return $self->html_tag('div', $content, @slurp);
287 }
288
289 sub ul_tag {
290   my ($self, $content, @slurp) = @_;
291   return $self->html_tag('ul', $content, @slurp);
292 }
293
294 sub li_tag {
295   my ($self, $content, @slurp) = @_;
296   return $self->html_tag('li', $content, @slurp);
297 }
298
299 sub link {
300   my ($self, $href, $content, @slurp) = @_;
301   my %params = _hashify(@slurp);
302
303   $href ||= '#';
304
305   return $self->html_tag('a', $content, %params, href => $href);
306 }
307
308 sub submit_tag {
309   my ($self, $name, $value, @slurp) = @_;
310   my %attributes = _hashify(@slurp);
311
312   if ( $attributes{confirm} ) {
313     $attributes{onclick} = 'return confirm("'. _J(delete($attributes{confirm})) .'");';
314   }
315
316   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
317 }
318
319 sub button_tag {
320   my ($self, $onclick, $value, @slurp) = @_;
321   my %attributes = _hashify(@slurp);
322
323   $attributes{id}   ||= $self->name_to_id($attributes{name}) if $attributes{name};
324   $attributes{type} ||= 'button';
325
326   return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
327 }
328
329 sub yes_no_tag {
330   my ($self, $name, $value) = splice @_, 0, 3;
331   my %attributes            = _hashify(@_);
332
333   return $self->select_tag($name, [ [ 1 => $::locale->text('Yes') ], [ 0 => $::locale->text('No') ] ], default => $value ? 1 : 0, %attributes);
334 }
335
336 sub javascript {
337   my ($self, $data) = @_;
338   return $self->html_tag('script', $data, type => 'text/javascript');
339 }
340
341 sub stylesheet_tag {
342   my $self = shift;
343   my $code = '';
344
345   foreach my $file (@_) {
346     $file .= '.css'        unless $file =~ m/\.css$/;
347     $file  = "css/${file}" unless $file =~ m|/|;
348
349     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
350   }
351
352   return $code;
353 }
354
355 my $date_tag_id_idx = 0;
356 sub date_tag {
357   my ($self, $name, $value, @slurp) = @_;
358
359   my %params   = _hashify(@slurp);
360   my $id       = $self->name_to_id($name) . _tag_id();
361   my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
362   my @class    = $params{no_cal} || $params{readonly} ? () : (class => 'datepicker');
363
364   return $self->input_tag(
365     $name, blessed($value) ? $value->to_lxoffice : $value,
366     id     => $id,
367     size   => 11,
368     onblur => "check_right_date_format(this);",
369     %params,
370     @class, @onchange,
371   );
372 }
373
374 sub customer_picker {
375   my ($self, $name, $value, %params) = @_;
376   my $name_e    = _H($name);
377
378   $::request->{layout}->add_javascripts('autocomplete_customer.js');
379
380   $self->hidden_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'customer_autocomplete') .
381   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params);
382 }
383
384 # simple version with select_tag
385 sub vendor_selector {
386   my ($self, $name, $value, %params) = @_;
387
388   my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
389                          (ref $value && $value->can('id')) ? $value->id : '';
390
391   return $self->select_tag($name, SL::DB::Manager::Vendor->get_all(),
392                                   default      => $actual_vendor_id,
393                                   title_sub    => sub { $_[0]->vendornumber . " : " . $_[0]->name },
394                                   'with_empty' => 1,
395                                   %params);
396 }
397
398
399 # simple version with select_tag
400 sub part_selector {
401   my ($self, $name, $value, %params) = @_;
402
403   my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
404                        (ref $value && $value->can('id')) ? $value->id : '';
405
406   return $self->select_tag($name, SL::DB::Manager::Part->get_all(),
407                            default      => $actual_part_id,
408                            title_sub    => sub { $_[0]->partnumber . " : " . $_[0]->description },
409                            with_empty   => 1,
410                            %params);
411 }
412
413
414 sub javascript_tag {
415   my $self = shift;
416   my $code = '';
417
418   foreach my $file (@_) {
419     $file .= '.js'        unless $file =~ m/\.js$/;
420     $file  = "js/${file}" unless $file =~ m|/|;
421
422     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
423   }
424
425   return $code;
426 }
427
428 sub tabbed {
429   my ($self, $tabs, @slurp) = @_;
430   my %params   = _hashify(@slurp);
431   my $id       = $params{id} || 'tab_' . _tag_id();
432
433   $params{selected} *= 1;
434
435   die 'L.tabbed needs an arrayred of tabs for first argument'
436     unless ref $tabs eq 'ARRAY';
437
438   my (@header, @blocks);
439   for my $i (0..$#$tabs) {
440     my $tab = $tabs->[$i];
441
442     next if $tab eq '';
443
444     my $tab_id = "__tab_id_$i";
445     push @header, $self->li_tag($self->link('#' . $tab_id, $tab->{name}));
446     push @blocks, $self->div_tag($tab->{data}, id => $tab_id);
447   }
448
449   return '' unless @header;
450
451   my $ul = $self->ul_tag(join('', @header), id => $id);
452   return $self->div_tag(join('', $ul, @blocks), class => 'tabwidget');
453 }
454
455 sub tab {
456   my ($self, $name, $src, @slurp) = @_;
457   my %params = _hashify(@slurp);
458
459   $params{method} ||= 'process';
460
461   return () if defined $params{if} && !$params{if};
462
463   my $data;
464   if ($params{method} eq 'raw') {
465     $data = $src;
466   } elsif ($params{method} eq 'process') {
467     $data = $self->_context->process($src, %{ $params{args} || {} });
468   } else {
469     die "unknown tag method '$params{method}'";
470   }
471
472   return () unless $data;
473
474   return +{ name => $name, data => $data };
475 }
476
477 sub areainput_tag {
478   my ($self, $name, $value, @slurp) = @_;
479   my %attributes      = _hashify(@slurp);
480
481   my ($rows, $cols);
482   my $min  = delete $attributes{min_rows} || 1;
483
484   if (exists $attributes{cols}) {
485     $cols = delete $attributes{cols};
486     $rows = $::form->numtextrows($value, $cols);
487   } else {
488     $rows = delete $attributes{rows} || 1;
489   }
490
491   return $rows > 1
492     ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
493     : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
494 }
495
496 sub multiselect2side {
497   my ($self, $id, @slurp) = @_;
498   my %params              = _hashify(@slurp);
499
500   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
501   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
502   $params{moveOptions}    = 'false';
503
504   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
505   my $code                = <<EOCODE;
506 <script type="text/javascript">
507   \$().ready(function() {
508     \$('#${id}').multiselect2side({ ${vars} });
509   });
510 </script>
511 EOCODE
512
513   return $code;
514 }
515
516 sub sortable_element {
517   my ($self, $selector, @slurp) = @_;
518   my %params                    = _hashify(@slurp);
519
520   my %attributes = ( distance => 5,
521                      helper   => <<'JAVASCRIPT' );
522     function(event, ui) {
523       ui.children().each(function() {
524         $(this).width($(this).width());
525       });
526       return ui;
527     }
528 JAVASCRIPT
529
530   my $stop_event = '';
531
532   if ($params{url} && $params{with}) {
533     my $as      = $params{as} || $params{with};
534     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
535     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
536
537     $stop_event = <<JAVASCRIPT;
538         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
539 JAVASCRIPT
540   }
541
542   if (!$params{dont_recolor}) {
543     $stop_event .= <<JAVASCRIPT;
544         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
545         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
546 JAVASCRIPT
547   }
548
549   if ($stop_event) {
550     $attributes{stop} = <<JAVASCRIPT;
551       function(event, ui) {
552         ${stop_event}
553         return ui;
554       }
555 JAVASCRIPT
556   }
557
558   $params{handle}     = '.dragdrop' unless exists $params{handle};
559   $attributes{handle} = "'$params{handle}'" if $params{handle};
560
561   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
562
563   my $code = <<JAVASCRIPT;
564 <script type="text/javascript">
565   \$(function() {
566     \$( "${selector}" ).sortable({ ${attr_str} })
567   });
568 </script>
569 JAVASCRIPT
570
571   return $code;
572 }
573
574 sub online_help_tag {
575   my ($self, $tag, @slurp) = @_;
576   my %params               = _hashify(@slurp);
577   my $cc                   = $::myconfig{countrycode};
578   my $file                 = "doc/online/$cc/$tag.html";
579   my $text                 = $params{text} || $::locale->text('Help');
580
581   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
582   return unless -f $file;
583   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
584 }
585
586 sub dump {
587   my $self = shift;
588   require Data::Dumper;
589   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
590 }
591
592 sub truncate {
593   my $self = shift;
594   return _call_presenter('truncate', @_);
595 }
596
597 sub sortable_table_header {
598   my ($self, $by, @slurp) = @_;
599   my %params              = _hashify(@slurp);
600
601   my $controller          = $self->{CONTEXT}->stash->get('SELF');
602   my $sort_spec           = $controller->get_sort_spec;
603   my $by_spec             = $sort_spec->{$by};
604   my %current_sort_params = $controller->get_current_sort_params;
605   my ($image, $new_dir)   = ('', $current_sort_params{dir});
606   my $title               = delete($params{title}) || $::locale->text($by_spec->{title});
607
608   if ($current_sort_params{by} eq $by) {
609     my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
610     $image          = '<img border="0" src="image/' . $current_dir . '.png">';
611     $new_dir        = 1 - ($current_sort_params{dir} || 0);
612   }
613
614   $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
615   $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
616
617   return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
618 }
619
620 sub paginate_controls {
621   my ($self)          = @_;
622
623   my $controller      = $self->{CONTEXT}->stash->get('SELF');
624   my $paginate_spec   = $controller->get_paginate_spec;
625   my %paginate_params = $controller->get_current_paginate_params;
626
627   my %template_params = (
628     pages             => \%paginate_params,
629     url_maker         => sub {
630       my %url_params                                    = _hashify(@_);
631       $url_params{ $paginate_spec->{FORM_PARAMS}->[0] } = delete $url_params{page};
632       $url_params{ $paginate_spec->{FORM_PARAMS}->[1] } = delete $url_params{per_page} if exists $url_params{per_page};
633
634       return $controller->get_callback(%url_params);
635     },
636   );
637
638   return SL::Presenter->get->render('common/paginate', %template_params);
639 }
640
641 sub simple_format {
642   my $self = shift;
643   return _call_presenter('simple_format', @_);
644 }
645
646 1;
647
648 __END__
649
650 =head1 NAME
651
652 SL::Templates::Plugin::L -- Layouting / tag generation
653
654 =head1 SYNOPSIS
655
656 Usage from a template:
657
658   [% USE L %]
659
660   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
661
662   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
663                                  { direction => 'right', display => 'To the right' } ],
664                                value_key => 'direction', title_key => 'display', default => 'right')) %]
665
666   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
667                                  { direction => 'right', display => 'To the right', selected => 1 } ],
668                                value_key => 'direction', title_key => 'display')) %]
669
670 =head1 DESCRIPTION
671
672 A module modeled a bit after Rails' ActionView helpers. Several small
673 functions that create HTML tags from various kinds of data sources.
674
675 =head1 FUNCTIONS
676
677 =head2 LOW-LEVEL FUNCTIONS
678
679 =over 4
680
681 =item C<name_to_id $name>
682
683 Converts a name to a HTML id by replacing various characters.
684
685 =item C<attributes %items>
686
687 Creates a string from all elements in C<%items> suitable for usage as
688 HTML tag attributes. Keys and values are HTML escaped even though keys
689 must not contain non-ASCII characters for browsers to accept them.
690
691 =item C<html_tag $tag_name, $content_string, %attributes>
692
693 Creates an opening and closing HTML tag for C<$tag_name> and puts
694 C<$content_string> between the two. If C<$content_string> is undefined
695 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
696 are key/value pairs added to the opening tag.
697
698 C<$content_string> is not HTML escaped.
699
700 =back
701
702 =head2 HIGH-LEVEL FUNCTIONS
703
704 =over 4
705
706 =item C<select_tag $name, \@collection, %attributes>
707
708 Creates a HTML 'select' tag named C<$name> with the contents of one
709 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
710 HTML attributes from C<%attributes>. The value
711 to use and the title to display are extracted from the elements in
712 C<\@collection>. Each element can be one of four things:
713
714 =over 12
715
716 =item 1. An array reference with at least two elements. The first element is
717 the value, the second element is its title. The third element is optional and and should contain a boolean.
718 If it is true, than the element will be used as default.
719
720 =item 2. A scalar. The scalar is both the value and the title.
721
722 =item 3. A hash reference. In this case C<%attributes> must contain
723 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
724 for the value, title and default respectively.
725
726 =item 4. A blessed reference. In this case C<%attributes> must contain
727 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
728 reference whose return values are used as the value, title and default
729 respectively.
730
731 =back
732
733 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
734 C<$attributes{title_key}> defaults to C<$attributes{value_key}>
735 and C<$attributes{default_key}> defaults to C<selected>.
736
737 In addition to pure keys/method you can also provide coderefs as I<value_sub>
738 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
739 and are called with the element as first argument. It must return the value, title or default.
740
741 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
742 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
743 element and must return a list of value and title.
744
745 If the option C<with_empty> is set then an empty element (value
746 C<undef>) will be used as the first element. The title to display for
747 this element can be set with the option C<empty_title> and defaults to
748 an empty string.
749
750 The option C<default> can be either a scalar or an array reference
751 containing the values of the options which should be set to be
752 selected.
753
754 The tag's C<id> defaults to C<name_to_id($name)>.
755
756 If the option C<with_optgroups> is set then this function expects
757 C<\@collection> to be one level deeper. The upper-most level is
758 translated into a HTML C<optgroup> tag. So the structure becomes:
759
760 =over 4
761
762 =item 1. Array of array references. Each element in the
763 C<\@collection> is converted into an optgroup.
764
765 =item 2. The optgroup's C<label> attribute will be set to the the
766 first element in the array element. The second array element is then
767 converted to a list of C<option> tags like it is described above.
768
769 =back
770
771 Example for use of optgroups:
772
773   # First in a controller:
774   my @collection = (
775     [ t8("First optgroup with two items"),
776       [ { id => 42, name => "item one" },
777         { id => 54, name => "second item" },
778         { id => 23, name => "and the third one" },
779       ] ],
780     [ t8("Another optgroup, with a lot of items from Rose"),
781       SL::DB::Manager::Customer->get_all_sorted ],
782   );
783
784   # Later in the template:
785   [% L.select_tag('the_selection', COLLECTION, with_optgroups=1, title_key='name') %]
786
787 =item C<yes_no_tag $name, $value, %attributes>
788
789 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
790 calling L<select_tag>. C<$value> determines
791 which entry is selected. The C<%attributes> are passed through to
792 L<select_tag>.
793
794 =item C<input_tag $name, $value, %attributes>
795
796 Creates a HTML 'input type=text' tag named C<$name> with the value
797 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
798 tag's C<id> defaults to C<name_to_id($name)>.
799
800 =item C<hidden_tag $name, $value, %attributes>
801
802 Creates a HTML 'input type=hidden' tag named C<$name> with the value
803 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
804 tag's C<id> defaults to C<name_to_id($name)>.
805
806 =item C<submit_tag $name, $value, %attributes>
807
808 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
809 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
810 tag's C<id> defaults to C<name_to_id($name)>.
811
812 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
813 be added via the C<onclick> handler asking the question given with
814 C<$attributes{confirm}>. If request is only submitted if the user
815 clicks the dialog's ok/yes button.
816
817 =item C<textarea_tag $name, $value, %attributes>
818
819 Creates a HTML 'textarea' tag named C<$name> with the content
820 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
821 tag's C<id> defaults to C<name_to_id($name)>.
822
823 =item C<checkbox_tag $name, %attributes>
824
825 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
826 HTML attributes from C<%attributes>. The tag's C<id> defaults to
827 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
828
829 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
830 created with said C<label>. No attribute named C<label> is created in
831 that case.
832
833 If C<%attributes> contains a key C<checkall> then the value is taken as a
834 JQuery selector and clicking this checkbox will also toggle all checkboxes
835 matching the selector.
836
837 =item C<date_tag $name, $value, %attributes>
838
839 Creates a date input field, with an attached javascript that will open a
840 calendar on click.
841
842 =item C<radio_button_tag $name, %attributes>
843
844 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
845 HTML attributes from C<%attributes>. The tag's C<value> defaults to
846 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
847
848 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
849 created with said C<label>. No attribute named C<label> is created in
850 that case.
851
852 =item C<javascript_tag $file1, $file2, $file3...>
853
854 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
855 tag for each file name parameter passed. Each file name will be
856 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
857 doesn't contain a slash.
858
859 =item C<stylesheet_tag $file1, $file2, $file3...>
860
861 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
862 for each file name parameter passed. Each file name will be postfixed
863 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
864 contain a slash.
865
866 =item C<tabbed \@tab, %attributes>
867
868 Will create a tabbed area. The tabs should be created with the helper function
869 C<tab>. Example:
870
871   [% L.tabbed([
872     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
873     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
874   ]) %]
875
876 =item C<areainput_tag $name, $content, %PARAMS>
877
878 Creates a generic input tag or textarea tag, depending on content size. The
879 amount of desired rows must be either given with the C<rows> parameter or can
880 be computed from the value and the C<cols> paramter, Accepted parameters
881 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
882
883 You can force input by setting rows to 1, and you can force textarea by setting
884 rows to anything >1.
885
886 =item C<multiselect2side $id, %params>
887
888 Creates a JavaScript snippet calling the jQuery function
889 C<multiselect2side> on the select control with the ID C<$id>. The
890 select itself is not created. C<%params> can contain the following
891 entries:
892
893 =over 2
894
895 =item C<labelsx>
896
897 The label of the list of available options. Defaults to the
898 translation of 'Available'.
899
900 =item C<labeldx>
901
902 The label of the list of selected options. Defaults to the
903 translation of 'Selected'.
904
905 =back
906
907 =item C<sortable_element $selector, %params>
908
909 Makes the children of the DOM element C<$selector> (a jQuery selector)
910 sortable with the I<jQuery UI Selectable> library. The children can be
911 dragged & dropped around. After dropping an element an URL can be
912 postet to with the element IDs of the sorted children.
913
914 If this is used then the JavaScript file C<js/jquery-ui.js> must be
915 included manually as well as it isn't loaded via C<$::form-gt;header>.
916
917 C<%params> can contain the following entries:
918
919 =over 2
920
921 =item C<url>
922
923 The URL to POST an AJAX request to after a dragged element has been
924 dropped. The AJAX request's return value is ignored. If given then
925 C<$params{with}> must be given as well.
926
927 =item C<with>
928
929 A string that is interpreted as the prefix of the children's ID. Upon
930 POSTing the result each child whose ID starts with C<$params{with}> is
931 considered. The prefix and the following "_" is removed from the
932 ID. The remaining parts of the IDs of those children are posted as a
933 single array parameter. The array parameter's name is either
934 C<$params{as}> or, missing that, C<$params{with}>.
935
936 =item C<as>
937
938 Sets the POST parameter name for AJAX request after dropping an
939 element (see C<$params{with}>).
940
941 =item C<handle>
942
943 An optional jQuery selector specifying which part of the child element
944 is dragable. If the parameter is not given then it defaults to
945 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
946 parameter is set and empty then the whole child element is dragable,
947 and clicks through to underlying elements like inputs or links might
948 not work.
949
950 =item C<dont_recolor>
951
952 If trueish then the children will not be recolored. The default is to
953 recolor the children by setting the class C<listrow0> on odd and
954 C<listrow1> on even entries.
955
956 =back
957
958 Example:
959
960   <script type="text/javascript" src="js/jquery-ui.js"></script>
961
962   <table id="thing_list">
963     <thead>
964       <tr><td>This</td><td>That</td></tr>
965     </thead>
966     <tbody>
967       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
968       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
969       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
970     </tbody>
971   <table>
972
973   [% L.sortable_element('#thing_list tbody',
974                         url          => 'controller.pl?action=SystemThings/reorder',
975                         with         => 'thingy',
976                         as           => 'thing_ids',
977                         recolor_rows => 1) %]
978
979 After dropping e.g. the third element at the top of the list a POST
980 request would be made to the C<reorder> action of the C<SystemThings>
981 controller with a single parameter called C<thing_ids> -- an array
982 containing the values C<[ 6, 2, 15 ]>.
983
984 =item C<dump REF>
985
986 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
987
988 =item C<sortable_table_header $by, %params>
989
990 Create a link and image suitable for placement in a table
991 header. C<$by> must be an index set up by the controller with
992 L<SL::Controller::Helper::make_sorted>.
993
994 The optional parameter C<$params{title}> can override the column title
995 displayed to the user. Otherwise the column title from the
996 controller's sort spec is used.
997
998 The other parameters in C<%params> are passed unmodified to the
999 underlying call to L<SL::Controller::Base::url_for>.
1000
1001 See the documentation of L<SL::Controller::Helper::Sorted> for an
1002 overview and further usage instructions.
1003
1004 =item C<paginate_controls>
1005
1006 Create a set of links used to paginate a list view.
1007
1008 See the documentation of L<SL::Controller::Helper::Paginated> for an
1009 overview and further usage instructions.
1010
1011 =back
1012
1013 =head2 CONVERSION FUNCTIONS
1014
1015 =over 4
1016
1017 =item C<tab, description, target, %PARAMS>
1018
1019 Creates a tab for C<tabbed>. The description will be used as displayed name.
1020 The target should be a block or template that can be processed. C<tab> supports
1021 a C<method> parameter, which can override the process method to apply target.
1022 C<method => 'raw'> will just include the given text as is. I was too lazy to
1023 implement C<include> properly.
1024
1025 Also an C<if> attribute is supported, so that tabs can be suppressed based on
1026 some occasion. In this case the supplied block won't even get processed, and
1027 the resulting tab will get ignored by C<tabbed>:
1028
1029   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
1030
1031 =item C<truncate $text, [%params]>
1032
1033 See L<SL::Presenter::Text/truncate>.
1034
1035 =item C<simple_format $text>
1036
1037 See L<SL::Presenter::Text/simple_format>.
1038
1039 =back
1040
1041 =head1 MODULE AUTHORS
1042
1043 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
1044
1045 L<http://linet-services.de>