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