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