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