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