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