L.truncate: Funktion zum intelligenten Verkürzen eines Strings
[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 1;
586
587 __END__
588
589 =head1 NAME
590
591 SL::Templates::Plugin::L -- Layouting / tag generation
592
593 =head1 SYNOPSIS
594
595 Usage from a template:
596
597   [% USE L %]
598
599   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
600
601   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
602                                                       { direction => 'right', display => 'To the right' } ],
603                                                     value => 'direction', title => 'display', default => 'right')) %]
604
605 =head1 DESCRIPTION
606
607 A module modeled a bit after Rails' ActionView helpers. Several small
608 functions that create HTML tags from various kinds of data sources.
609
610 =head1 FUNCTIONS
611
612 =head2 LOW-LEVEL FUNCTIONS
613
614 =over 4
615
616 =item C<name_to_id $name>
617
618 Converts a name to a HTML id by replacing various characters.
619
620 =item C<attributes %items>
621
622 Creates a string from all elements in C<%items> suitable for usage as
623 HTML tag attributes. Keys and values are HTML escaped even though keys
624 must not contain non-ASCII characters for browsers to accept them.
625
626 =item C<html_tag $tag_name, $content_string, %attributes>
627
628 Creates an opening and closing HTML tag for C<$tag_name> and puts
629 C<$content_string> between the two. If C<$content_string> is undefined
630 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
631 are key/value pairs added to the opening tag.
632
633 C<$content_string> is not HTML escaped.
634
635 =back
636
637 =head2 HIGH-LEVEL FUNCTIONS
638
639 =over 4
640
641 =item C<select_tag $name, $options_string, %attributes>
642
643 Creates a HTML 'select' tag named C<$name> with the contents
644 C<$options_string> and with arbitrary HTML attributes from
645 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
646
647 The C<$options_string> is usually created by the
648 L</options_for_select> function. If C<$options_string> is an array
649 reference then it will be passed to L</options_for_select>
650 automatically.
651
652 =item C<yes_no_tag $name, $value, %attributes>
653
654 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
655 calling L<select_tag> and L<options_for_select>. C<$value> determines
656 which entry is selected. The C<%attributes> are passed through to
657 L<select_tag>.
658
659 =item C<input_tag $name, $value, %attributes>
660
661 Creates a HTML 'input type=text' tag named C<$name> with the value
662 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
663 tag's C<id> defaults to C<name_to_id($name)>.
664
665 =item C<hidden_tag $name, $value, %attributes>
666
667 Creates a HTML 'input type=hidden' tag named C<$name> with the value
668 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
669 tag's C<id> defaults to C<name_to_id($name)>.
670
671 =item C<submit_tag $name, $value, %attributes>
672
673 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
674 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
675 tag's C<id> defaults to C<name_to_id($name)>.
676
677 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
678 be added via the C<onclick> handler asking the question given with
679 C<$attributes{confirm}>. If request is only submitted if the user
680 clicks the dialog's ok/yes button.
681
682 =item C<textarea_tag $name, $value, %attributes>
683
684 Creates a HTML 'textarea' tag named C<$name> with the content
685 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
686 tag's C<id> defaults to C<name_to_id($name)>.
687
688 =item C<checkbox_tag $name, %attributes>
689
690 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
691 HTML attributes from C<%attributes>. The tag's C<id> defaults to
692 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
693
694 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
695 created with said C<label>. No attribute named C<label> is created in
696 that case.
697
698 If C<%attributes> contains a key C<checkall> then the value is taken as a
699 JQuery selector and clicking this checkbox will also toggle all checkboxes
700 matching the selector.
701
702 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
703
704 Creates a date input field, with an attached javascript that will open a
705 calendar on click. The javascript ist by default anchoered at the bottom right
706 sight. This can be overridden with C<cal_align>, see Calendar documentation for
707 the details, usually you'll want a two letter abbreviation of the alignment.
708 Right + Bottom becomes C<BL>.
709
710 =item C<radio_button_tag $name, %attributes>
711
712 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
713 HTML attributes from C<%attributes>. The tag's C<value> defaults to
714 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
715
716 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
717 created with said C<label>. No attribute named C<label> is created in
718 that case.
719
720 =item C<javascript_tag $file1, $file2, $file3...>
721
722 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
723 tag for each file name parameter passed. Each file name will be
724 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
725 doesn't contain a slash.
726
727 =item C<stylesheet_tag $file1, $file2, $file3...>
728
729 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
730 for each file name parameter passed. Each file name will be postfixed
731 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
732 contain a slash.
733
734 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
735
736 Creates a date input field, with an attached javascript that will open a
737 calendar on click. The javascript ist by default anchoered at the bottom right
738 sight. This can be overridden with C<cal_align>, see Calendar documentation for
739 the details, usually you'll want a two letter abbreviation of the alignment.
740 Right + Bottom becomes C<BL>.
741
742 =item C<tabbed \@tab, %attributes>
743
744 Will create a tabbed area. The tabs should be created with the helper function
745 C<tab>. Example:
746
747   [% L.tabbed([
748     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
749     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
750   ]) %]
751
752 An optional attribute is C<selected>, which accepts the ordinal of a tab which
753 should be selected by default.
754
755 =item C<areainput_tag $name, $content, %PARAMS>
756
757 Creates a generic input tag or textarea tag, depending on content size. The
758 amount of desired rows must be either given with the C<rows> parameter or can
759 be computed from the value and the C<cols> paramter, Accepted parameters
760 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
761
762 You can force input by setting rows to 1, and you can force textarea by setting
763 rows to anything >1.
764
765 =item C<multiselect2side $id, %params>
766
767 Creates a JavaScript snippet calling the jQuery function
768 C<multiselect2side> on the select control with the ID C<$id>. The
769 select itself is not created. C<%params> can contain the following
770 entries:
771
772 =over 2
773
774 =item C<labelsx>
775
776 The label of the list of available options. Defaults to the
777 translation of 'Available'.
778
779 =item C<labeldx>
780
781 The label of the list of selected options. Defaults to the
782 translation of 'Selected'.
783
784 =back
785
786 =item C<sortable_element $selector, %params>
787
788 Makes the children of the DOM element C<$selector> (a jQuery selector)
789 sortable with the I<jQuery UI Selectable> library. The children can be
790 dragged & dropped around. After dropping an element an URL can be
791 postet to with the element IDs of the sorted children.
792
793 If this is used then the JavaScript file C<js/jquery-ui.js> must be
794 included manually as well as it isn't loaded via C<$::form-gt;header>.
795
796 C<%params> can contain the following entries:
797
798 =over 2
799
800 =item C<url>
801
802 The URL to POST an AJAX request to after a dragged element has been
803 dropped. The AJAX request's return value is ignored. If given then
804 C<$params{with}> must be given as well.
805
806 =item C<with>
807
808 A string that is interpreted as the prefix of the children's ID. Upon
809 POSTing the result each child whose ID starts with C<$params{with}> is
810 considered. The prefix and the following "_" is removed from the
811 ID. The remaining parts of the IDs of those children are posted as a
812 single array parameter. The array parameter's name is either
813 C<$params{as}> or, missing that, C<$params{with}>.
814
815 =item C<as>
816
817 Sets the POST parameter name for AJAX request after dropping an
818 element (see C<$params{with}>).
819
820 =item C<handle>
821
822 An optional jQuery selector specifying which part of the child element
823 is dragable. If the parameter is not given then it defaults to
824 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
825 parameter is set and empty then the whole child element is dragable,
826 and clicks through to underlying elements like inputs or links might
827 not work.
828
829 =item C<dont_recolor>
830
831 If trueish then the children will not be recolored. The default is to
832 recolor the children by setting the class C<listrow0> on odd and
833 C<listrow1> on even entries.
834
835 =back
836
837 Example:
838
839   <script type="text/javascript" src="js/jquery-ui.js"></script>
840
841   <table id="thing_list">
842     <thead>
843       <tr><td>This</td><td>That</td></tr>
844     </thead>
845     <tbody>
846       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
847       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
848       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
849     </tbody>
850   <table>
851
852   [% L.sortable_element('#thing_list tbody',
853                         url          => 'controller.pl?action=SystemThings/reorder',
854                         with         => 'thingy',
855                         as           => 'thing_ids',
856                         recolor_rows => 1) %]
857
858 After dropping e.g. the third element at the top of the list a POST
859 request would be made to the C<reorder> action of the C<SystemThings>
860 controller with a single parameter called C<thing_ids> -- an array
861 containing the values C<[ 6, 2, 15 ]>.
862
863 =item C<dump REF>
864
865 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
866
867 =back
868
869 =head2 CONVERSION FUNCTIONS
870
871 =over 4
872
873 =item C<options_for_select \@collection, %options>
874
875 Creates a string suitable for a HTML 'select' tag consisting of one
876 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
877 to use and the title to display are extracted from the elements in
878 C<\@collection>. Each element can be one of four things:
879
880 =over 12
881
882 =item 1. An array reference with at least two elements. The first element is
883 the value, the second element is its title.
884
885 =item 2. A scalar. The scalar is both the value and the title.
886
887 =item 3. A hash reference. In this case C<%options> must contain
888 I<value> and I<title> keys that name the keys in the element to use
889 for the value and title respectively.
890
891 =item 4. A blessed reference. In this case C<%options> must contain
892 I<value> and I<title> keys that name functions called on the blessed
893 reference whose return values are used as the value and title
894 respectively.
895
896 =back
897
898 For cases 3 and 4 C<$options{value}> defaults to C<id> and
899 C<$options{title}> defaults to C<$options{value}>.
900
901 In addition to pure keys/method you can also provide coderefs as I<value_sub>
902 and/or I<title_sub>. If present, these take precedence over keys or methods,
903 and are called with the element as first argument. It must return the value or
904 title.
905
906 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
907 precedence over each individual sub. It will only be called once for each
908 element and must return a list of value and title.
909
910 If the option C<with_empty> is set then an empty element (value
911 C<undef>) will be used as the first element. The title to display for
912 this element can be set with the option C<empty_title> and defaults to
913 an empty string.
914
915 The option C<default> can be either a scalar or an array reference
916 containing the values of the options which should be set to be
917 selected.
918
919 =item C<tab, description, target, %PARAMS>
920
921 Creates a tab for C<tabbed>. The description will be used as displayed name.
922 The target should be a block or template that can be processed. C<tab> supports
923 a C<method> parameter, which can override the process method to apply target.
924 C<method => 'raw'> will just include the given text as is. I was too lazy to
925 implement C<include> properly.
926
927 Also an C<if> attribute is supported, so that tabs can be suppressed based on
928 some occasion. In this case the supplied block won't even get processed, and
929 the resulting tab will get ignored by C<tabbed>:
930
931   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
932
933 =item C<truncate $text, %params>
934
935 Returns the C<$text> truncated after a certain number of
936 characters.
937
938 The number of characters to truncate at is determined by the parameter
939 C<at> which defaults to 50. If the text is longer than C<$params{at}>
940 then it will be truncated and postfixed with '...'. Otherwise it will
941 be returned unmodified.
942
943 =back
944
945 =head1 MODULE AUTHORS
946
947 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
948
949 L<http://linet-services.de>