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