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