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