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