L: hidden_tag(): Parameterübergabe gefixt
[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 sub date_tag {
362   my ($self, $name, $value, @slurp) = @_;
363   my %params   = _hashify(@slurp);
364   my $name_e   = _H($name);
365   my $seq      = _tag_id();
366   my $datefmt  = apply {
367     s/d+/\%d/gi;
368     s/m+/\%m/gi;
369     s/y+/\%Y/gi;
370   } $::myconfig{"dateformat"};
371
372   my $cal_align = delete $params{cal_align} || 'BR';
373   my $onchange  = delete $params{onchange};
374   my $str_value = blessed $value ? $value->to_lxoffice : $value;
375
376   $self->input_tag($name, $str_value,
377     id     => $name_e,
378     size   => 11,
379     title  => _H($::myconfig{dateformat}),
380     onBlur => 'check_right_date_format(this)',
381     ($onchange ? (
382     onChange => $onchange,
383     ) : ()),
384     %params,
385   ) . ((!$params{no_cal} && !$params{readonly}) ?
386   $self->html_tag('img', undef,
387     src    => 'image/calendar.png',
388     alt    => $::locale->text('Calendar'),
389     id     => "trigger$seq",
390     title  => _H($::myconfig{dateformat}),
391     %params,
392   ) .
393   $self->javascript(
394     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
395   ) : '');
396 }
397
398 sub customer_picker {
399   my ($self, $name, $value, %params) = @_;
400   my $name_e    = _H($name);
401
402   $::request->{layout}->add_javascripts('autocomplete_customer.js');
403
404   $self->hidden_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'customer_autocomplete') .
405   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params);
406 }
407
408 # simple version with select_tag
409 sub vendor_selector {
410   my ($self, $name, $value, %params) = @_;
411
412   my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
413                          (ref $value && $value->can('id')) ? $value->id : '';
414
415   return $self->select_tag($name, SL::DB::Manager::Vendor->get_all(),
416                                   default      => $actual_vendor_id,
417                                   title_sub    => sub { $_[0]->vendornumber . " : " . $_[0]->name },
418                                   'with_empty' => 1,
419                                   %params);
420 }
421
422
423 # simple version with select_tag
424 sub part_selector {
425   my ($self, $name, $value, %params) = @_;
426
427   my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
428                        (ref $value && $value->can('id')) ? $value->id : '';
429
430   return $self->select_tag($name, SL::DB::Manager::Part->get_all(),
431                            default      => $actual_part_id,
432                            title_sub    => sub { $_[0]->partnumber . " : " . $_[0]->description },
433                            with_empty   => 1,
434                            %params);
435 }
436
437
438 sub javascript_tag {
439   my $self = shift;
440   my $code = '';
441
442   foreach my $file (@_) {
443     $file .= '.js'        unless $file =~ m/\.js$/;
444     $file  = "js/${file}" unless $file =~ m|/|;
445
446     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
447   }
448
449   return $code;
450 }
451
452 sub tabbed {
453   my ($self, $tabs, @slurp) = @_;
454   my %params   = _hashify(@slurp);
455   my $id       = $params{id} || 'tab_' . _tag_id();
456
457   $params{selected} *= 1;
458
459   die 'L.tabbed needs an arrayred of tabs for first argument'
460     unless ref $tabs eq 'ARRAY';
461
462   my (@header, @blocks);
463   for my $i (0..$#$tabs) {
464     my $tab = $tabs->[$i];
465
466     next if $tab eq '';
467
468     my $tab_id = "__tab_id_$i";
469     push @header, $self->li_tag($self->link('#' . $tab_id, $tab->{name}));
470     push @blocks, $self->div_tag($tab->{data}, id => $tab_id);
471   }
472
473   return '' unless @header;
474
475   my $ul = $self->ul_tag(join('', @header), id => $id);
476   return $self->div_tag(join('', $ul, @blocks), class => 'tabwidget');
477 }
478
479 sub tab {
480   my ($self, $name, $src, @slurp) = @_;
481   my %params = _hashify(@slurp);
482
483   $params{method} ||= 'process';
484
485   return () if defined $params{if} && !$params{if};
486
487   my $data;
488   if ($params{method} eq 'raw') {
489     $data = $src;
490   } elsif ($params{method} eq 'process') {
491     $data = $self->_context->process($src, %{ $params{args} || {} });
492   } else {
493     die "unknown tag method '$params{method}'";
494   }
495
496   return () unless $data;
497
498   return +{ name => $name, data => $data };
499 }
500
501 sub areainput_tag {
502   my ($self, $name, $value, @slurp) = @_;
503   my %attributes      = _hashify(@slurp);
504
505   my ($rows, $cols);
506   my $min  = delete $attributes{min_rows} || 1;
507
508   if (exists $attributes{cols}) {
509     $cols = delete $attributes{cols};
510     $rows = $::form->numtextrows($value, $cols);
511   } else {
512     $rows = delete $attributes{rows} || 1;
513   }
514
515   return $rows > 1
516     ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
517     : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
518 }
519
520 sub multiselect2side {
521   my ($self, $id, @slurp) = @_;
522   my %params              = _hashify(@slurp);
523
524   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
525   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
526   $params{moveOptions}    = 'false';
527
528   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
529   my $code                = <<EOCODE;
530 <script type="text/javascript">
531   \$().ready(function() {
532     \$('#${id}').multiselect2side({ ${vars} });
533   });
534 </script>
535 EOCODE
536
537   return $code;
538 }
539
540 sub sortable_element {
541   my ($self, $selector, @slurp) = @_;
542   my %params                    = _hashify(@slurp);
543
544   my %attributes = ( distance => 5,
545                      helper   => <<'JAVASCRIPT' );
546     function(event, ui) {
547       ui.children().each(function() {
548         $(this).width($(this).width());
549       });
550       return ui;
551     }
552 JAVASCRIPT
553
554   my $stop_event = '';
555
556   if ($params{url} && $params{with}) {
557     my $as      = $params{as} || $params{with};
558     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
559     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
560
561     $stop_event = <<JAVASCRIPT;
562         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
563 JAVASCRIPT
564   }
565
566   if (!$params{dont_recolor}) {
567     $stop_event .= <<JAVASCRIPT;
568         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
569         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
570 JAVASCRIPT
571   }
572
573   if ($stop_event) {
574     $attributes{stop} = <<JAVASCRIPT;
575       function(event, ui) {
576         ${stop_event}
577         return ui;
578       }
579 JAVASCRIPT
580   }
581
582   $params{handle}     = '.dragdrop' unless exists $params{handle};
583   $attributes{handle} = "'$params{handle}'" if $params{handle};
584
585   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
586
587   my $code = <<JAVASCRIPT;
588 <script type="text/javascript">
589   \$(function() {
590     \$( "${selector}" ).sortable({ ${attr_str} })
591   });
592 </script>
593 JAVASCRIPT
594
595   return $code;
596 }
597
598 sub online_help_tag {
599   my ($self, $tag, @slurp) = @_;
600   my %params               = _hashify(@slurp);
601   my $cc                   = $::myconfig{countrycode};
602   my $file                 = "doc/online/$cc/$tag.html";
603   my $text                 = $params{text} || $::locale->text('Help');
604
605   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
606   return unless -f $file;
607   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
608 }
609
610 sub dump {
611   my $self = shift;
612   require Data::Dumper;
613   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
614 }
615
616 sub truncate {
617   my ($self, $text, @slurp) = @_;
618   my %params                = _hashify(@slurp);
619
620   $params{at}             ||= 50;
621   $params{at}               =  3 if 3 > $params{at};
622   $params{at}              -= 3;
623
624   return $text if length($text) < $params{at};
625   return substr($text, 0, $params{at}) . '...';
626 }
627
628 sub sortable_table_header {
629   my ($self, $by, @slurp) = @_;
630   my %params              = _hashify(@slurp);
631
632   my $controller          = $self->{CONTEXT}->stash->get('SELF');
633   my $sort_spec           = $controller->get_sort_spec;
634   my $by_spec             = $sort_spec->{$by};
635   my %current_sort_params = $controller->get_current_sort_params;
636   my ($image, $new_dir)   = ('', $current_sort_params{dir});
637   my $title               = delete($params{title}) || $::locale->text($by_spec->{title});
638
639   if ($current_sort_params{by} eq $by) {
640     my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
641     $image          = '<img border="0" src="image/' . $current_dir . '.png">';
642     $new_dir        = 1 - ($current_sort_params{dir} || 0);
643   }
644
645   $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
646   $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
647
648   return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
649 }
650
651 sub paginate_controls {
652   my ($self)          = @_;
653
654   my $controller      = $self->{CONTEXT}->stash->get('SELF');
655   my $paginate_spec   = $controller->get_paginate_spec;
656   my %paginate_params = $controller->get_current_paginate_params;
657
658   my %template_params = (
659     pages             => \%paginate_params,
660     url_maker         => sub {
661       my %url_params                                    = _hashify(@_);
662       $url_params{ $paginate_spec->{FORM_PARAMS}->[0] } = delete $url_params{page};
663       $url_params{ $paginate_spec->{FORM_PARAMS}->[1] } = delete $url_params{per_page} if exists $url_params{per_page};
664
665       return $controller->get_callback(%url_params);
666     },
667   );
668
669   return SL::Presenter->get->render('common/paginate', %template_params);
670 }
671
672 1;
673
674 __END__
675
676 =head1 NAME
677
678 SL::Templates::Plugin::L -- Layouting / tag generation
679
680 =head1 SYNOPSIS
681
682 Usage from a template:
683
684   [% USE L %]
685
686   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
687
688   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
689                                  { direction => 'right', display => 'To the right' } ],
690                                value_key => 'direction', title_key => 'display', default => 'right')) %]
691
692   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
693                                  { direction => 'right', display => 'To the right', selected => 1 } ],
694                                value_key => 'direction', title_key => 'display')) %]
695
696 =head1 DESCRIPTION
697
698 A module modeled a bit after Rails' ActionView helpers. Several small
699 functions that create HTML tags from various kinds of data sources.
700
701 =head1 FUNCTIONS
702
703 =head2 LOW-LEVEL FUNCTIONS
704
705 =over 4
706
707 =item C<name_to_id $name>
708
709 Converts a name to a HTML id by replacing various characters.
710
711 =item C<attributes %items>
712
713 Creates a string from all elements in C<%items> suitable for usage as
714 HTML tag attributes. Keys and values are HTML escaped even though keys
715 must not contain non-ASCII characters for browsers to accept them.
716
717 =item C<html_tag $tag_name, $content_string, %attributes>
718
719 Creates an opening and closing HTML tag for C<$tag_name> and puts
720 C<$content_string> between the two. If C<$content_string> is undefined
721 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
722 are key/value pairs added to the opening tag.
723
724 C<$content_string> is not HTML escaped.
725
726 =back
727
728 =head2 HIGH-LEVEL FUNCTIONS
729
730 =over 4
731
732 =item C<select_tag $name, \@collection, %attributes>
733
734 Creates a HTML 'select' tag named C<$name> with the contents of one
735 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
736 HTML attributes from C<%attributes>. The value
737 to use and the title to display are extracted from the elements in
738 C<\@collection>. Each element can be one of four things:
739
740 =over 12
741
742 =item 1. An array reference with at least two elements. The first element is
743 the value, the second element is its title. The third element is optional and and should contain a boolean.
744 If it is true, than the element will be used as default.
745
746 =item 2. A scalar. The scalar is both the value and the title.
747
748 =item 3. A hash reference. In this case C<%attributes> must contain
749 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
750 for the value, title and default respectively.
751
752 =item 4. A blessed reference. In this case C<%attributes> must contain
753 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
754 reference whose return values are used as the value, title and default
755 respectively.
756
757 =back
758
759 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
760 C<$attributes{title_key}> defaults to C<$attributes{value_key}>
761 and C<$attributes{default_key}> defaults to C<selected>.
762
763 In addition to pure keys/method you can also provide coderefs as I<value_sub>
764 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
765 and are called with the element as first argument. It must return the value, title or default.
766
767 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
768 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
769 element and must return a list of value and title.
770
771 If the option C<with_empty> is set then an empty element (value
772 C<undef>) will be used as the first element. The title to display for
773 this element can be set with the option C<empty_title> and defaults to
774 an empty string.
775
776 The option C<default> can be either a scalar or an array reference
777 containing the values of the options which should be set to be
778 selected.
779
780 The tag's C<id> defaults to C<name_to_id($name)>.
781
782 =item C<yes_no_tag $name, $value, %attributes>
783
784 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
785 calling L<select_tag>. C<$value> determines
786 which entry is selected. The C<%attributes> are passed through to
787 L<select_tag>.
788
789 =item C<input_tag $name, $value, %attributes>
790
791 Creates a HTML 'input type=text' tag named C<$name> with the value
792 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
793 tag's C<id> defaults to C<name_to_id($name)>.
794
795 =item C<hidden_tag $name, $value, %attributes>
796
797 Creates a HTML 'input type=hidden' tag named C<$name> with the value
798 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
799 tag's C<id> defaults to C<name_to_id($name)>.
800
801 =item C<submit_tag $name, $value, %attributes>
802
803 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
804 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
805 tag's C<id> defaults to C<name_to_id($name)>.
806
807 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
808 be added via the C<onclick> handler asking the question given with
809 C<$attributes{confirm}>. If request is only submitted if the user
810 clicks the dialog's ok/yes button.
811
812 =item C<textarea_tag $name, $value, %attributes>
813
814 Creates a HTML 'textarea' tag named C<$name> with the content
815 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
816 tag's C<id> defaults to C<name_to_id($name)>.
817
818 =item C<checkbox_tag $name, %attributes>
819
820 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
821 HTML attributes from C<%attributes>. The tag's C<id> defaults to
822 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
823
824 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
825 created with said C<label>. No attribute named C<label> is created in
826 that case.
827
828 If C<%attributes> contains a key C<checkall> then the value is taken as a
829 JQuery selector and clicking this checkbox will also toggle all checkboxes
830 matching the selector.
831
832 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
833
834 Creates a date input field, with an attached javascript that will open a
835 calendar on click. The javascript ist by default anchoered at the bottom right
836 sight. This can be overridden with C<cal_align>, see Calendar documentation for
837 the details, usually you'll want a two letter abbreviation of the alignment.
838 Right + Bottom becomes C<BL>.
839
840 =item C<radio_button_tag $name, %attributes>
841
842 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
843 HTML attributes from C<%attributes>. The tag's C<value> defaults to
844 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
845
846 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
847 created with said C<label>. No attribute named C<label> is created in
848 that case.
849
850 =item C<javascript_tag $file1, $file2, $file3...>
851
852 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
853 tag for each file name parameter passed. Each file name will be
854 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
855 doesn't contain a slash.
856
857 =item C<stylesheet_tag $file1, $file2, $file3...>
858
859 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
860 for each file name parameter passed. Each file name will be postfixed
861 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
862 contain a slash.
863
864 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
865
866 Creates a date input field, with an attached javascript that will open a
867 calendar on click. The javascript ist by default anchoered at the bottom right
868 sight. This can be overridden with C<cal_align>, see Calendar documentation for
869 the details, usually you'll want a two letter abbreviation of the alignment.
870 Right + Bottom becomes C<BL>.
871
872 =item C<tabbed \@tab, %attributes>
873
874 Will create a tabbed area. The tabs should be created with the helper function
875 C<tab>. Example:
876
877   [% L.tabbed([
878     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
879     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
880   ]) %]
881
882 =item C<areainput_tag $name, $content, %PARAMS>
883
884 Creates a generic input tag or textarea tag, depending on content size. The
885 amount of desired rows must be either given with the C<rows> parameter or can
886 be computed from the value and the C<cols> paramter, Accepted parameters
887 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
888
889 You can force input by setting rows to 1, and you can force textarea by setting
890 rows to anything >1.
891
892 =item C<multiselect2side $id, %params>
893
894 Creates a JavaScript snippet calling the jQuery function
895 C<multiselect2side> on the select control with the ID C<$id>. The
896 select itself is not created. C<%params> can contain the following
897 entries:
898
899 =over 2
900
901 =item C<labelsx>
902
903 The label of the list of available options. Defaults to the
904 translation of 'Available'.
905
906 =item C<labeldx>
907
908 The label of the list of selected options. Defaults to the
909 translation of 'Selected'.
910
911 =back
912
913 =item C<sortable_element $selector, %params>
914
915 Makes the children of the DOM element C<$selector> (a jQuery selector)
916 sortable with the I<jQuery UI Selectable> library. The children can be
917 dragged & dropped around. After dropping an element an URL can be
918 postet to with the element IDs of the sorted children.
919
920 If this is used then the JavaScript file C<js/jquery-ui.js> must be
921 included manually as well as it isn't loaded via C<$::form-gt;header>.
922
923 C<%params> can contain the following entries:
924
925 =over 2
926
927 =item C<url>
928
929 The URL to POST an AJAX request to after a dragged element has been
930 dropped. The AJAX request's return value is ignored. If given then
931 C<$params{with}> must be given as well.
932
933 =item C<with>
934
935 A string that is interpreted as the prefix of the children's ID. Upon
936 POSTing the result each child whose ID starts with C<$params{with}> is
937 considered. The prefix and the following "_" is removed from the
938 ID. The remaining parts of the IDs of those children are posted as a
939 single array parameter. The array parameter's name is either
940 C<$params{as}> or, missing that, C<$params{with}>.
941
942 =item C<as>
943
944 Sets the POST parameter name for AJAX request after dropping an
945 element (see C<$params{with}>).
946
947 =item C<handle>
948
949 An optional jQuery selector specifying which part of the child element
950 is dragable. If the parameter is not given then it defaults to
951 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
952 parameter is set and empty then the whole child element is dragable,
953 and clicks through to underlying elements like inputs or links might
954 not work.
955
956 =item C<dont_recolor>
957
958 If trueish then the children will not be recolored. The default is to
959 recolor the children by setting the class C<listrow0> on odd and
960 C<listrow1> on even entries.
961
962 =back
963
964 Example:
965
966   <script type="text/javascript" src="js/jquery-ui.js"></script>
967
968   <table id="thing_list">
969     <thead>
970       <tr><td>This</td><td>That</td></tr>
971     </thead>
972     <tbody>
973       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
974       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
975       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
976     </tbody>
977   <table>
978
979   [% L.sortable_element('#thing_list tbody',
980                         url          => 'controller.pl?action=SystemThings/reorder',
981                         with         => 'thingy',
982                         as           => 'thing_ids',
983                         recolor_rows => 1) %]
984
985 After dropping e.g. the third element at the top of the list a POST
986 request would be made to the C<reorder> action of the C<SystemThings>
987 controller with a single parameter called C<thing_ids> -- an array
988 containing the values C<[ 6, 2, 15 ]>.
989
990 =item C<dump REF>
991
992 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
993
994 =item C<sortable_table_header $by, %params>
995
996 Create a link and image suitable for placement in a table
997 header. C<$by> must be an index set up by the controller with
998 L<SL::Controller::Helper::make_sorted>.
999
1000 The optional parameter C<$params{title}> can override the column title
1001 displayed to the user. Otherwise the column title from the
1002 controller's sort spec is used.
1003
1004 The other parameters in C<%params> are passed unmodified to the
1005 underlying call to L<SL::Controller::Base::url_for>.
1006
1007 See the documentation of L<SL::Controller::Helper::Sorted> for an
1008 overview and further usage instructions.
1009
1010 =item C<paginate_controls>
1011
1012 Create a set of links used to paginate a list view.
1013
1014 See the documentation of L<SL::Controller::Helper::Paginated> for an
1015 overview and further usage instructions.
1016
1017 =back
1018
1019 =head2 CONVERSION FUNCTIONS
1020
1021 =over 4
1022
1023 =item C<tab, description, target, %PARAMS>
1024
1025 Creates a tab for C<tabbed>. The description will be used as displayed name.
1026 The target should be a block or template that can be processed. C<tab> supports
1027 a C<method> parameter, which can override the process method to apply target.
1028 C<method => 'raw'> will just include the given text as is. I was too lazy to
1029 implement C<include> properly.
1030
1031 Also an C<if> attribute is supported, so that tabs can be suppressed based on
1032 some occasion. In this case the supplied block won't even get processed, and
1033 the resulting tab will get ignored by C<tabbed>:
1034
1035   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
1036
1037 =item C<truncate $text, %params>
1038
1039 Returns the C<$text> truncated after a certain number of
1040 characters.
1041
1042 The number of characters to truncate at is determined by the parameter
1043 C<at> which defaults to 50. If the text is longer than C<$params{at}>
1044 then it will be truncated and postfixed with '...'. Otherwise it will
1045 be returned unmodified.
1046
1047 =back
1048
1049 =head1 MODULE AUTHORS
1050
1051 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
1052
1053 L<http://linet-services.de>