]> wagnertech.de Git - mfinanz.git/blob - SL/Presenter/Tag.pm
restart apache2 in postinst
[mfinanz.git] / SL / Presenter / Tag.pm
1 package SL::Presenter::Tag;
2
3 use strict;
4
5 use SL::HTML::Restrict;
6 use SL::Locale::String qw(t8);
7 use SL::Presenter::EscapedText qw(escape);
8 use Scalar::Util qw(blessed);
9
10 use Exporter qw(import);
11 our @EXPORT_OK = qw(
12   html_tag input_tag hidden_tag javascript man_days_tag name_to_id select_tag
13   checkbox_tag button_tag submit_tag ajax_submit_tag input_number_tag
14   stringify_attributes textarea_tag link_tag date_tag
15   div_tag radio_button_tag img_tag multi_level_select_tag input_tag_trim
16   input_email_tag);
17 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
18
19 use Carp;
20
21 my %_valueless_attributes = map { $_ => 1 } qw(
22   checked compact declare defer disabled ismap multiple noresize noshade nowrap
23   readonly selected hidden
24 );
25
26 my %_singleton_tags = map { $_ => 1 } qw(
27   area base br col command embed hr img input keygen link meta param source
28   track wbr
29 );
30
31 sub _call_on {
32   my ($object, $method, @params) = @_;
33   return $object->$method(@params);
34 }
35
36 { # This will give you an id for identifying html tags and such.
37   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
38   # Do not use these id's to store information across requests.
39 my $_id_sequence = int rand 1e7;
40 sub _id {
41   return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
42 }
43 }
44
45 sub _J {
46   my $string = shift;
47   $string    =~ s/(\"|\'|\\)/\\$1/g;
48   return $string;
49 }
50
51 sub join_values {
52   my ($name, $value) = @_;
53   my $spacer = $name eq 'class' ? ' ' : ''; # join classes with spaces, everything else as is
54
55   ref $value && 'ARRAY' eq ref $value
56   ? join $spacer, map { join_values($name, $_) } @$value
57   : $value
58 }
59
60 sub stringify_attributes {
61   my (%params) = @_;
62
63   my @result = ();
64   while (my ($name, $value) = each %params) {
65     next unless $name;
66     next if $_valueless_attributes{$name} && !$value;
67     $value = '' if !defined($value);
68     $value = join_values($name, $value) if ref $value && 'ARRAY' eq ref $value;
69     push @result, $_valueless_attributes{$name} ? escape($name) : escape($name) . '="' . escape($value) . '"';
70   }
71
72   return @result ? ' ' . join(' ', @result) : '';
73 }
74
75 sub html_tag {
76   my ($tag, $content, %params) = @_;
77   my $attributes = stringify_attributes(%params);
78
79   return "<${tag}${attributes}>" if !defined($content) && $_singleton_tags{$tag};
80   return "<${tag}${attributes}>${content}</${tag}>";
81 }
82
83 sub input_tag {
84   my ($name, $value, %attributes) = @_;
85
86   _set_id_attribute(\%attributes, $name);
87   $attributes{type} ||= 'text';
88
89   html_tag('input', undef, %attributes, name => $name, value => $value);
90 }
91
92 sub input_tag_trim {
93   my ($name, $value, %attributes) = @_;
94   $attributes{'data-validate'} .= ' trimmed_whitespaces';
95
96   _set_id_attribute(\%attributes, $name);
97   $::request->layout->add_javascripts('kivi.Validator.js');
98   $::request->presenter->need_reinit_widgets($attributes{id});
99
100   input_tag($name, $value, %attributes);
101 }
102
103 sub hidden_tag {
104   my ($name, $value, %attributes) = @_;
105   input_tag($name, $value, %attributes, type => 'hidden');
106 }
107
108 sub man_days_tag {
109   my ($name, $object, %attributes) = @_;
110
111   my $size           =  delete($attributes{size})   || 5;
112   my $method         =  $name;
113   $method            =~ s/^.*\.//;
114
115   my $time_selection = input_tag("${name}_as_man_days_string", _call_on($object, "${method}_as_man_days_string"), %attributes, size => $size);
116   my $unit_selection = select_tag("${name}_as_man_days_unit",   [[ 'h', $::locale->text('h') ], [ 'man_day', $::locale->text('MD') ]],
117                                           %attributes, default => _call_on($object, "${method}_as_man_days_unit"));
118
119   return $time_selection . $unit_selection;
120 }
121
122 sub name_to_id {
123   my ($name) = @_;
124
125   $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
126   $name =~ s/[^\w_]/_/g;
127   $name =~ s/_+/_/g;
128
129   return $name;
130 }
131
132 sub select_tag {
133   my ($name, $collection, %attributes) = @_;
134
135   _set_id_attribute(\%attributes, $name);
136
137   $collection         = [] if defined($collection) && !ref($collection) && ($collection eq '');
138
139   my $with_filter     = delete($attributes{with_filter});
140   my $fil_placeholder = delete($attributes{filter_placeholder});
141   my $value_key       = delete($attributes{value_key})   || 'id';
142   my $title_key       = delete($attributes{title_key})   || $value_key;
143   my $default_key     = delete($attributes{default_key}) || 'selected';
144   my $default_val_key = delete($attributes{default_value_key});
145   my $default_coll    = delete($attributes{default});
146
147   my $value_title_sub = delete($attributes{value_title_sub});
148
149   my $value_sub       = delete($attributes{value_sub});
150   my $title_sub       = delete($attributes{title_sub});
151   my $default_sub     = delete($attributes{default_sub});
152
153   my $with_empty      = delete($attributes{with_empty});
154   my $empty_title     = delete($attributes{empty_title});
155
156   my $with_optgroups  = delete($attributes{with_optgroups});
157
158   undef $default_key if $default_sub || $default_val_key;
159
160   my $normalize_entry = sub {
161     my ($type, $entry, $sub, $key) = @_;
162
163     return $sub->($entry) if $sub;
164
165     my $ref = ref($entry);
166
167     if ( !$ref ) {
168       return $entry if $type eq 'value' || $type eq 'title';
169       return 0;
170     }
171
172     if ( $ref eq 'ARRAY' ) {
173       return $entry->[ $type eq 'value' ? 0 : $type eq 'title' ? 1 : 2 ];
174     }
175
176     return $entry->{$key} if $ref  eq 'HASH';
177     return $entry->$key   if $type ne 'default' || $entry->can($key);
178     return undef;
179   };
180
181   my %selected;
182   if (defined($default_coll) && !ref $default_coll) {
183     %selected = ($default_coll => 1);
184
185   } elsif (ref($default_coll) eq 'HASH') {
186     %selected = %{ $default_coll };
187
188   } elsif ($default_coll) {
189     $default_coll = [ $default_coll ] unless 'ARRAY' eq ref $default_coll;
190
191     %selected = $default_val_key ? map({ ($normalize_entry->('value', $_, undef, $default_val_key) => 1) } @{ $default_coll })
192               :                    map({ ($_                                                       => 1) } @{ $default_coll });
193   }
194
195   my $list_to_code = sub {
196     my ($sub_collection) = @_;
197
198     if ('ARRAY' ne ref $sub_collection) {
199       $sub_collection = [ $sub_collection ];
200     }
201
202     my @options;
203     foreach my $entry ( @{ $sub_collection } ) {
204       my $value;
205       my $title;
206
207       if ( $value_title_sub ) {
208         ($value, $title) = @{ $value_title_sub->($entry) };
209       } else {
210
211         $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
212         $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
213       }
214
215       my $default = $default_key ? $normalize_entry->('default', $entry, $default_sub, $default_key) : 0;
216
217       push(@options, [$value, $title, $selected{$value} || $default]);
218     }
219
220     return join '', map { html_tag('option', escape($_->[1]), value => $_->[0], selected => $_->[2]) } @options;
221   };
222
223   my $code  = '';
224   $code    .= html_tag('option', escape($empty_title || ''), value => '') if $with_empty;
225
226   if (!$with_optgroups) {
227     $code .= $list_to_code->($collection);
228
229   } else {
230     $code .= join '', map {
231       my ($optgroup_title, $sub_collection) = @{ $_ };
232       html_tag('optgroup', $list_to_code->($sub_collection), label => $optgroup_title)
233     } @{ $collection };
234   }
235
236   my $select_html = html_tag('select', $code, %attributes, name => $name);
237
238   if ($with_filter) {
239     my $input_style;
240
241     if (($attributes{style} // '') =~ m{width: *(\d+) *px}i) {
242       $input_style = "width: " . ($1 - 22) . "px";
243     }
244
245     my $input_html = html_tag(
246       'input', undef,
247       autocomplete     => 'off',
248       type             => 'text',
249       id               => $attributes{id} . '_filter',
250       'data-select-id' => $attributes{id},
251       (placeholder     => $fil_placeholder) x !!$fil_placeholder,
252       (style           => $input_style)     x !!$input_style,
253     );
254     $select_html = html_tag('div', $input_html . $select_html, class => "filtered_select");
255   }
256
257   return $select_html;
258 }
259
260 sub multi_level_select_tag {
261   my ($name, $collection, $levels, %attributes) = @_;
262
263   _set_id_attribute(\%attributes, $name);
264   my $id = $attributes{id};
265
266   $collection = [] if defined($collection) && !ref($collection) && ($collection eq '');
267
268   my $surround_tag = delete($attributes{surround_tag});
269
270   my $multi_level_select_tag = "";
271   my %level_keys = ();
272
273   my $base_attributes  = delete($attributes{"level_1"}) || {};
274
275   my $base_name        = delete($base_attributes->{name});
276   my $base_value_key   = $base_attributes->{value_key} || 'id';
277   my $base_title_key   = $base_attributes->{title_key} || $base_value_key;
278   my $base_default     = $base_attributes->{default};
279   my $base_default_key = $base_attributes->{default_key};
280
281   $level_keys{1} = {
282     "value" => $base_value_key,
283     "title" => $base_title_key,
284   };
285   $base_attributes->{id} = $id . "_level_1";
286   $base_attributes->{onchange} = $id . "_on_chanche_level_1(this)";
287   my $current_select = select_tag(
288     $base_name,
289     $collection,
290     %{$base_attributes}
291   );
292   if ($surround_tag) {
293     $multi_level_select_tag .= html_tag($surround_tag, $current_select);
294   } else {
295     $multi_level_select_tag .= $current_select;
296   }
297
298   my $last_object;
299   if ($base_default) {
300     ($last_object) = grep { $_->{$base_value_key} eq $base_default } @{$collection};
301   } elsif ($base_default_key) {
302     ($last_object) = grep { $_->{$base_default_key} } @{$collection};
303   } else {
304     $last_object = $collection->[0];
305   }
306   foreach my $level (2 .. $levels) {
307     my $current_attributes = delete($attributes{"level_$level"}) || {};
308
309     my $current_name          = delete($current_attributes->{name});
310     my $current_value_key     = $current_attributes->{value_key} || 'id';
311     my $current_title_key     = $current_attributes->{title_key} || $current_value_key;
312     my $current_default       = $current_attributes->{default};
313     my $current_default_key   = $current_attributes->{default_key};
314     my $current_object_key    = delete($current_attributes->{object_key});
315     die "need object_key in level_$level in multi_level_select_tag" unless $current_object_key;
316
317     $level_keys{$level} = {
318       "value"  => $current_value_key,
319       "title"  => $current_title_key,
320       "object" => $current_object_key,
321     };
322
323     $current_attributes->{id} = $id . "_level_$level";
324     $current_attributes->{onchange} = $id . "_on_chanche_level_${level}(this)" unless $level eq $levels;
325     my $current_collection = $last_object->{$current_object_key};
326     my $current_select = select_tag(
327       $current_name,
328       $current_collection,
329       %{$current_attributes}
330     );
331     if ($surround_tag) {
332       $multi_level_select_tag .= html_tag($surround_tag, $current_select);
333     } else {
334       $multi_level_select_tag .= $current_select;
335     }
336
337     if ($current_default) {
338       ($last_object) = grep { $_->{$current_value_key} eq $current_default } @{$current_collection};
339     } elsif ($current_default_key) {
340       ($last_object) = grep { $_->{$current_default_key} } @{$current_collection};
341     } else {
342       $last_object = $current_collection->[0];
343     }
344   }
345
346   my $code = "";
347
348   if ($levels > 1) {
349     my $js_option_string = ""; # js hash map (level->value->{[value: 1, title: 'foo'], ...})
350
351     my @current_collections = @{$collection};
352     my @next_collections = ();
353     foreach my $level (1 .. ($levels - 1)) {
354       $js_option_string .= "'${level}': {";
355       foreach my $option (@current_collections) {
356         $js_option_string .= "'" . $option->{$level_keys{$level}{value}} . "': [";
357         map { $js_option_string .= "{ value: '" . $_->{$level_keys{$level + 1}{value}}
358                                 .  "', title: '" . $_->{$level_keys{$level + 1}{title}}
359                                 .  "'}," } @{$option->{$level_keys{$level + 1}{object}}};
360         $js_option_string .= "],";
361         push @next_collections, @{$option->{$level_keys{$level + 1}{object}}};
362       }
363       $js_option_string .= "},";
364       @current_collections = @next_collections;
365       @next_collections = ();
366     }
367
368     $code .= javascript( qq|
369       var ${id}_options = {
370         $js_option_string
371       };
372     |);
373     foreach my $level (1 .. ($levels - 1)) {
374       my $next_level = $level + 1;
375       $code .= javascript( qq|
376       function ${id}_on_chanche_level_${level}(select_${level}) {
377         var value = select_${level}.value;
378         for (var i = ${next_level}; i < $levels + 1; i++) {
379           var id = '${id}_level_' + i;
380           var select_i = document.getElementById(id);
381           while (select_i.options.length) {
382             select_i.options.remove(0);
383           }
384           select_i.disabled = true;
385         }
386         var id_${next_level} = '${id}_level_${next_level}';
387         var select_${next_level} = document.getElementById(id_${next_level});
388         for (var i=0; i < ${id}_options[${level}][value].length; i++) {
389           var option = new Option(${id}_options[${level}][value][i].title, ${id}_options[${level}][value][i].value)
390           select_${next_level}.options.add(option);
391         }
392         select_${next_level}.disabled = false;
393         select_${next_level}.selectedIndex = -1;
394       }
395       |);
396     }
397   }
398
399   return $multi_level_select_tag . $code;
400 }
401
402 sub checkbox_tag {
403   my ($name, %attributes) = @_;
404
405   my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
406   delete @attributes{grep { m{^label_} } keys %attributes};
407
408   _set_id_attribute(\%attributes, $name);
409
410   $attributes{value}   = 1 unless defined $attributes{value};
411   my $label            = delete $attributes{label};
412   my $checkall         = delete $attributes{checkall};
413   my $for_submit       = delete $attributes{for_submit};
414
415   if ($attributes{checked}) {
416     $attributes{checked} = 'checked';
417   } else {
418     delete $attributes{checked};
419   }
420
421   my $code  = '';
422   $code    .= hidden_tag($name, 0, %attributes, id => $attributes{id} . '_hidden') if $for_submit;
423   $code    .= html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
424   $code    .= html_tag('label', $label, for => $attributes{id}, %label_attributes) if $label;
425   $code    .= javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
426
427   return $code;
428 }
429
430 sub radio_button_tag {
431   my ($name, %attributes) = @_;
432
433   my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
434   delete @attributes{grep { m{^label_} } keys %attributes};
435
436   $attributes{value}   = 1 unless exists $attributes{value};
437
438   _set_id_attribute(\%attributes, $name, 1);
439   my $label            = delete $attributes{label};
440
441   _set_id_attribute(\%attributes, $name . '_' . $attributes{value});
442
443   if ($attributes{checked}) {
444     $attributes{checked} = 'checked';
445   } else {
446     delete $attributes{checked};
447   }
448
449   my $code  = html_tag('input', undef,  %attributes, name => $name, type => 'radio');
450   $code    .= html_tag('label', $label, for => $attributes{id}, %label_attributes) if $label;
451
452   return $code;
453 }
454
455 sub button_tag {
456   my ($onclick, $value, %attributes) = @_;
457
458   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
459   $attributes{type} ||= 'button';
460   $attributes{tag}  ||= 'input';
461
462   $onclick = 'if (!confirm("'. _J(delete($attributes{confirm})) .'")) return false; ' . $onclick if $attributes{confirm};
463
464   if ( $attributes{tag} eq 'input' ) {
465     html_tag('input', undef, %attributes, value => $value, (onclick => $onclick)x!!$onclick)
466   }
467   elsif ( $attributes{tag} eq 'button' ) {
468     html_tag('button', undef, %attributes, value => $value, (onclick => $onclick)x!!$onclick);
469   }
470 }
471
472 sub submit_tag {
473   my ($name, $value, %attributes) = @_;
474
475   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
476
477   if ( $attributes{confirm} ) {
478     $attributes{onclick} = 'return confirm("'. _J(delete($attributes{confirm})) .'");';
479   }
480
481   input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
482 }
483
484 sub ajax_submit_tag {
485   my ($url, $form_selector, $text, %attributes) = @_;
486
487   $url           = _J($url);
488   $form_selector = _J($form_selector);
489   my $onclick    = qq|kivi.submit_ajax_form('${url}', '${form_selector}')|;
490
491   button_tag($onclick, $text, %attributes);
492 }
493
494 sub input_number_tag {
495   my ($name, $value, %params) = @_;
496
497   _set_id_attribute(\%params, $name);
498   my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
499   my @classes  = ('numeric');
500   push @classes, delete($params{class}) if $params{class};
501   my %class    = @classes ? (class => join(' ', @classes)) : ();
502
503   $::request->layout->add_javascripts('kivi.Validator.js');
504   $::request->presenter->need_reinit_widgets($params{id});
505
506   input_tag(
507     $name, $::form->format_amount(\%::myconfig, $value, $params{precision}),
508     "data-validate" => "number",
509     %params,
510     %class, @onchange,
511   );
512 }
513
514 sub input_email_tag {
515   my ($name, $value, %params) = @_;
516
517   my $show_icon = $value || delete($params{show_icon_always});
518
519   # _set_id_attribute removes the no_id param
520   my $no_id_wanted = $params{no_id};
521   _set_id_attribute(\%params, $name);
522   $params{no_id} = $no_id_wanted;
523
524   my $html = input_tag_trim($name, $value, %params);
525
526   my $link_id   = $params{id} ? $params{id} . '_link' : undef;
527   $html        .= link_tag(escape('mailto:' . $value),
528                            img_tag(src => 'image/mail.png', alt => t8('Send email'), border => 0),
529                            (id    => $link_id)x!!$link_id,
530                            (style => 'display:none')x!$show_icon);
531
532   return '<span>' . $html . '</span>';
533 }
534
535 sub javascript {
536   my ($data) = @_;
537   html_tag('script', $data, type => 'text/javascript');
538 }
539
540 sub _set_id_attribute {
541   my ($attributes, $name, $unique) = @_;
542
543   if (!delete($attributes->{no_id}) && !$attributes->{id}) {
544     $attributes->{id}  = name_to_id($name);
545     $attributes->{id} .= '_' . $attributes->{value} if $unique;
546   }
547
548   %{ $attributes };
549 }
550
551 my $html_restricter;
552
553 sub textarea_tag {
554   my ($name, $content, %attributes) = @_;
555
556   _set_id_attribute(\%attributes, $name);
557   $attributes{rows}  *= 1; # required by standard
558   $attributes{cols}  *= 1; # required by standard
559
560   if (join_values(class => $attributes{class}) =~ /\btexteditor\b/) {
561     $::request->{layout}->add_javascripts("$_.js") for qw(ckeditor5/ckeditor ckeditor5/translations/de);
562   }
563
564   html_tag('textarea', $content, %attributes, name => $name);
565 }
566
567 sub link_tag {
568   my ($href, $content, %params) = @_;
569
570   $href ||= '#';
571
572   html_tag('a', $content, %params, href => $href);
573 }
574 # alias for compatibility
575 sub link { goto &link_tag }
576
577 sub date_tag {
578   my ($name, $value, %params) = @_;
579
580   _set_id_attribute(\%params, $name);
581   my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
582   my @classes  = $params{no_cal} || $params{readonly} ? () : ('datepicker');
583   push @classes, delete($params{class}) if $params{class};
584   my %class    = @classes ? (class => join(' ', @classes)) : ();
585
586   $::request->layout->add_javascripts('kivi.Validator.js');
587   $::request->presenter->need_reinit_widgets($params{id});
588
589   $params{'data-validate'} = join(' ', "date", grep { $_ } (delete $params{'data-validate'}));
590
591   input_tag(
592     $name, blessed($value) ? $value->to_lxoffice : $value,
593     size   => 11,
594     %params,
595     %class, @onchange,
596   );
597 }
598
599 sub div_tag {
600   my ($content, %params) = @_;
601   return html_tag('div', $content, %params);
602 }
603
604 sub img_tag {
605   my (%params) = @_;
606
607   $params{alt} ||= '';
608
609   return html_tag('img', undef, %params);
610 }
611
612 1;
613 __END__
614
615 =pod
616
617 =encoding utf8
618
619 =head1 NAME
620
621 SL::Presenter::Tag - Layouting / tag generation
622
623 =head1 SYNOPSIS
624
625 Usage in a template:
626
627   [% USE P %]
628
629   [% P.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
630
631   [% P.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
632                                  { direction => 'right', display => 'To the right' } ],
633                                value_key => 'direction', title_key => 'display', default => 'right') %]
634
635   [% P.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
636                                  { direction => 'right', display => 'To the right', selected => 1 } ],
637                                value_key => 'direction', title_key => 'display') %]
638
639   # Use an RDBO object and its n:m relationship as the default
640   # values. For example, a user can be a member of many groups. "All
641   # groups" is therefore the full collection and "$user->groups" is a
642   # list of RDBO AuthGroup objects whose IDs must match the ones in
643   # "All groups". This could look like the following:
644   [% P.select_tag('user.groups[]', SELF.all_groups, multiple=1,
645                   default=SELF.user.groups, default_value_key='id' ) %]
646
647 =head1 DESCRIPTION
648
649 A module modeled a bit after Rails' ActionView helpers. Several small
650 functions that create HTML tags from various kinds of data sources.
651
652 The C<id> attribute is usually calculated automatically. This can be
653 overridden by either specifying an C<id> attribute or by setting
654 C<no_id> to trueish.
655
656 =head1 FUNCTIONS
657
658 =head2 LOW-LEVEL FUNCTIONS
659
660 =over 4
661
662 =item C<html_tag $tag_name, $content_string, %attributes>
663
664 Creates an opening and closing HTML tag for C<$tag_name> and puts
665 C<$content_string> between the two. If C<$content_string> is undefined
666 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
667 are key/value pairs added to the opening tag.
668
669 C<$content_string> is not HTML escaped.
670
671 =item C<name_to_id $name>
672
673 Converts a name to a HTML id by replacing various characters.
674
675 =item C<stringify_attributes %items>
676
677 Creates a string from all elements in C<%items> suitable for usage as
678 HTML tag attributes. Keys and values are HTML escaped even though keys
679 must not contain non-ASCII characters for browsers to accept them.
680
681 =back
682
683 =head2 HIGH-LEVEL FUNCTIONS
684
685 =over 4
686
687 =item C<input_tag $name, $value, %attributes>
688
689 Creates a HTML 'input type=text' tag named C<$name> with the value
690 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
691 tag's C<id> defaults to C<name_to_id($name)>.
692
693 =item C<input_tag_trim $name, $value, %attributes>
694
695 This is a wrapper around C<input_tag> that adds ' trimmed_whitespaces' to
696 $attributes{'data-validate'} and loads C<js/kivi.Validator.js>. This will trim
697 the whitespaces around the input.
698
699 =item C<input_email_tag $name, $value, %params>
700
701 This creates an C<input_tag_trim> and a C<link_tag> with an C<img_tag> for an
702 email icon. The link opens a 'mailto:' url with the value of the C<input_tag>.
703 The style of the C<link_tag> is set to 'display:none' if there is no value or
704 if the param C<show_icon_always> is truish.
705 All but the C<show_icon_always> params are forwared to the C<input_tag_trim>
706 as attributes.
707 This tag does not offer any javascript magic. The input value is only
708 evaluated when the tag is created.
709 The 'id' attrubute of the C<link_tag> is set to the 'id' of the C<input_tag>
710 with the string '_link' appended. This can be used to show/hide set the
711 C<link_tag> or set the link target dynamically.
712
713 =item C<submit_tag $name, $value, %attributes>
714
715 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
716 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
717 tag's C<id> defaults to C<name_to_id($name)>.
718
719 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
720 be added via the C<onclick> handler asking the question given with
721 C<$attributes{confirm}>. The request is only submitted if the user
722 clicks the dialog's ok/yes button.
723
724 =item C<ajax_submit_tag $url, $form_selector, $text, %attributes>
725
726 Creates a HTML 'input type="button"' tag with a very specific onclick
727 handler that submits the form given by the jQuery selector
728 C<$form_selector> to the URL C<$url> (the actual JavaScript function
729 called for that is C<kivi.submit_ajax_form()> in
730 C<js/client_js.js>). The button's label will be C<$text>.
731
732 =item C<button_tag $onclick, $text, %attributes>
733
734 Creates a HTML 'input type="button"' tag with an onclick handler
735 C<$onclick> and a value of C<$text>. The button does not have a name
736 nor an ID by default.
737
738 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
739 be prepended to the C<$onclick> handler asking the question given with
740 C<$attributes{confirm}>. The request is only submitted if the user
741 clicks the dialog's "ok/yes" button.
742
743 =item C<man_days_tag $name, $object, %attributes>
744
745 Creates two HTML inputs: a text input for entering a number and a drop
746 down box for chosing the unit (either 'man days' or 'hours').
747
748 C<$object> must be a L<Rose::DB::Object> instance using the
749 L<SL::DB::Helper::AttrDuration> helper.
750
751 C<$name> is supposed to be the name of the underlying column,
752 e.g. C<time_estimation> for an instance of
753 C<SL::DB::RequirementSpecItem>. If C<$name> has the form
754 C<prefix.method> then the full C<$name> is used for the input's base
755 names while the methods called on C<$object> are only the suffix. This
756 makes it possible to write statements like e.g.
757
758   [% P.man_days_tag("requirement_spec_item.time_estimation", SELF.item) %]
759
760 The attribute C<size> can be used to set the text input's size. It
761 defaults to 5.
762
763 =item C<hidden_tag $name, $value, %attributes>
764
765 Creates a HTML 'input type=hidden' tag named C<$name> with the value
766 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
767 tag's C<id> defaults to C<name_to_id($name)>.
768
769 =item C<checkbox_tag $name, %attributes>
770
771 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
772 HTML attributes from C<%attributes>. The tag's C<id> defaults to
773 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
774
775 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
776 created with said C<label>. No attribute named C<label> is created in
777 that case. Furthermore, all attributes whose names start with
778 C<label_> become attributes on the label tag without the C<label_>
779 prefix. For example, C<label_style='#ff0000'> will be turned into
780 C<style='#ff0000'> on the label tag, causing the text to become red.
781
782 If C<%attributes> contains a key C<checkall> then the value is taken as a
783 JQuery selector and clicking this checkbox will also toggle all checkboxes
784 matching the selector.
785
786 =item C<radio_button_tag $name, %attributes>
787
788 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
789 HTML attributes from C<%attributes>. The tag's C<value> defaults to
790 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
791
792 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
793 created with said C<label>. No attribute named C<label> is created in
794 that case. Furthermore, all attributes whose names start with
795 C<label_> become attributes on the label tag without the C<label_>
796 prefix. For example, C<label_style='#ff0000'> will be turned into
797 C<style='#ff0000'> on the label tag, causing the text to become red.
798
799 =item C<select_tag $name, \@collection, %attributes>
800
801 Creates an HTML 'select' tag named C<$name> with the contents of one
802 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
803 HTML attributes from C<%attributes>. The value
804 to use and the title to display are extracted from the elements in
805 C<\@collection>. Each element can be one of four things:
806
807 =over 12
808
809 =item 1. An array reference with at least two elements. The first element is
810 the value, the second element is its title. The third element is optional and and should contain a boolean.
811 If it is true, than the element will be used as default.
812
813 =item 2. A scalar. The scalar is both the value and the title.
814
815 =item 3. A hash reference. In this case C<%attributes> must contain
816 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
817 for the value, title and default respectively.
818
819 =item 4. A blessed reference. In this case C<%attributes> must contain
820 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
821 reference whose return values are used as the value, title and default
822 respectively.
823
824 =back
825
826 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
827 C<$attributes{title_key}> defaults to C<$attributes{value_key}> and
828 C<$attributes{default_key}> defaults to C<selected>. Note that
829 C<$attributes{default_key}> is set to C<undef> if
830 C<$attributes{default_value_key}> is used as well (see below).
831
832 In addition to pure keys/method you can also provide coderefs as I<value_sub>
833 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
834 and are called with the element as first argument. It must return the value, title or default.
835
836 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
837 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
838 element and must return a list of value and title.
839
840 If the option C<with_empty> is set then an empty element (value
841 C<undef>) will be used as the first element. The title to display for
842 this element can be set with the option C<empty_title> and defaults to
843 an empty string.
844
845 The tag's C<id> defaults to C<name_to_id($name)>.
846
847 The option C<default> can be quite a lot of things:
848
849 =over 4
850
851 =item 1. A scalar value. This is the value of the entry that's
852 selected by default.
853
854 =item 2. A hash reference for C<multiple=1>. Whether or not an entry
855 is selected by default is looked up in this hash.
856
857 =item 3. An array reference containing scalar values. Same as 1., just
858 for the case of C<multiple=1>.
859
860 =item 4. If C<default_value_key> is given: an array reference of hash
861 references. For each hash reference the value belonging to the key
862 C<default_value_key> is treated as one value to select by
863 default. Constructs a hash that's treated like 3.
864
865 =item 5. If C<default_value_key> is given: an array reference of
866 blessed objects. For each object the value returne from calling the
867 function named C<default_value_key> on the object is treated as one
868 value to select by default. Constructs a hash that's treated like 3.
869
870 =back
871
872 5. also applies to single RDBO instances (due to 'wantarray'
873 shenanigans assigning RDBO's relationships to a hash key will result
874 in a single RDBO object being assigned instead of an array reference
875 containing that single RDBO object).
876
877 If the option C<with_optgroups> is set then this function expects
878 C<\@collection> to be one level deeper. The upper-most level is
879 translated into an HTML C<optgroup> tag. So the structure becomes:
880
881 =over 4
882
883 =item 1. Array of array references. Each element in the
884 C<\@collection> is converted into an optgroup.
885
886 =item 2. The optgroup's C<label> attribute will be set to the
887 first element in the array element. The second array element is then
888 converted to a list of C<option> tags as described above.
889
890 =back
891
892 Example for use of optgroups:
893
894   # First in a controller:
895   my @collection = (
896     [ t8("First optgroup with three items"),
897       [ { id => 42, name => "item one" },
898         { id => 54, name => "second item" },
899         { id => 23, name => "and the third one" },
900       ] ],
901     [ t8("Another optgroup, with a lot of items from Rose"),
902       SL::DB::Manager::Customer->get_all_sorted ],
903   );
904
905   # Later in the template:
906   [% L.select_tag('the_selection', COLLECTION, with_optgroups=1, title_key='name') %]
907
908 =item C<multi_level_select_tag $name, \@collection, $levels, %attributes>
909
910 Creates multiple HTML 'select' tags, one for each level. Each tag ist created
911 with C<select_tag>.
912
913 The parameters for C<select_tag> are created from the values stored in the
914 corrosponding level attributes. The attributes for each level are in
915 C<%attributes{level_i}>, starting with 1.
916
917 The following are used directly from each level:
918
919 =over 4
920
921 =item I<name> is deleted from level attributes.
922 The I<name> is used for the name of the 'select' tag.
923
924 =item I<object_key> is deleted from level attriutes.
925 The I<object_key> is used to get the level objects for the current level
926 from the object of the previous level, this is intended for the use of RSDB object.
927 Each level attributes must contain I<object_key>, except level_1.
928
929 =item I<id> is overridden.
930 The I<id> is set to a specific value for use in JavaScript.
931
932 =item I<value_key> names the key for the value of level object. Defaults to
933 C<id>.
934
935 =item I<title_key> names the key for the titel of level object. Defaults to
936 C<$attributes{value_key}>.
937
938 =item I<default> is used to find the default level object.
939
940 =item I<default_key> names the key for the default feld of level object. Is
941 ignored if I<default> is set.
942
943 =item I<\@collection> is used for the level object of level 1. The objects of the next
944 level are taken from the previous level via the <object_key>. On each level the
945 objects must be a hash reference or a blessed reference.
946
947 =back
948
949 A I<surround_tag> can be specified, which generates an C<html_tag> with the
950 corresponding tag around every C<select_tag>.
951
952 Example:
953
954   # First in a controller:
955     my @collection = (
956     { 'description' => 'foo_1', 'id' => '1',
957       'key_1' => [
958         { 'id' => 3, 'description' => "bar_1",
959           'key_2' => [
960             { 'id' => 1, 'value' => "foobar_1", },
961             { 'id' => 2, 'value' => "foobar_2", },
962           ], },
963         { 'id' => 4, 'description' => "bar_2",
964         'key_2' => [], },
965       ], },
966     { 'description' => 'foo_2', 'id' => '2',
967       'key_1' => [
968         { 'id' => 1, 'description' => "bar_1",
969           'key_2' => [
970             { 'id' => 3, 'value' => "foobar_3", },
971             { 'id' => 4, 'value' => "foobar_4", },
972           ], },
973         { 'id' => 2, 'description' => "bar_2",
974           'stock' => [
975             { 'id' => 5, 'value' => "test_5", },
976           ], },
977       ], },
978   );
979
980   # Later in the template (in a table):
981   [% L.multi_level_select_tag("multi_select_foo_bar_foobar", COLLECTION, 3,
982       surround_tag="td",
983       level_1={
984         name="foo.id",
985         value_key="id",
986         title_key="description",
987         default="2",
988       },
989       level_2={
990         object_key="key_1",
991         name="bar.id",
992         value_key="id",
993         title_key="description",
994         default="1",
995       },
996       level_3={
997         object_key="key_2",
998         name="foobar.id",
999         value_key="id",
1000         title_key="value",
1001         default="4",
1002       },
1003   ) %]
1004
1005 =back
1006
1007 =head1 BUGS
1008
1009 Nothing here yet.
1010
1011 =head1 AUTHOR
1012
1013 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
1014 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
1015
1016 =cut