1 package SL::Presenter::Tag;
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);
10 use Exporter qw(import);
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
17 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
21 my %_valueless_attributes = map { $_ => 1 } qw(
22 checked compact declare defer disabled ismap multiple noresize noshade nowrap
23 readonly selected hidden
26 my %_singleton_tags = map { $_ => 1 } qw(
27 area base br col command embed hr img input keygen link meta param source
32 my ($object, $method, @params) = @_;
33 return $object->$method(@params);
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;
41 return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
47 $string =~ s/(\"|\'|\\)/\\$1/g;
52 my ($name, $value) = @_;
53 my $spacer = $name eq 'class' ? ' ' : ''; # join classes with spaces, everything else as is
55 ref $value && 'ARRAY' eq ref $value
56 ? join $spacer, map { join_values($name, $_) } @$value
60 sub stringify_attributes {
64 while (my ($name, $value) = each %params) {
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) . '"';
72 return @result ? ' ' . join(' ', @result) : '';
76 my ($tag, $content, %params) = @_;
77 my $attributes = stringify_attributes(%params);
79 return "<${tag}${attributes}>" if !defined($content) && $_singleton_tags{$tag};
80 return "<${tag}${attributes}>${content}</${tag}>";
84 my ($name, $value, %attributes) = @_;
86 _set_id_attribute(\%attributes, $name);
87 $attributes{type} ||= 'text';
89 html_tag('input', undef, %attributes, name => $name, value => $value);
93 my ($name, $value, %attributes) = @_;
94 $attributes{'data-validate'} .= ' trimmed_whitespaces';
96 _set_id_attribute(\%attributes, $name);
97 $::request->layout->add_javascripts('kivi.Validator.js');
98 $::request->presenter->need_reinit_widgets($attributes{id});
100 input_tag($name, $value, %attributes);
104 my ($name, $value, %attributes) = @_;
105 input_tag($name, $value, %attributes, type => 'hidden');
109 my ($name, $object, %attributes) = @_;
111 my $size = delete($attributes{size}) || 5;
113 $method =~ s/^.*\.//;
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"));
119 return $time_selection . $unit_selection;
125 $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
126 $name =~ s/[^\w_]/_/g;
133 my ($name, $collection, %attributes) = @_;
135 _set_id_attribute(\%attributes, $name);
137 $collection = [] if defined($collection) && !ref($collection) && ($collection eq '');
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});
147 my $value_title_sub = delete($attributes{value_title_sub});
149 my $value_sub = delete($attributes{value_sub});
150 my $title_sub = delete($attributes{title_sub});
151 my $default_sub = delete($attributes{default_sub});
153 my $with_empty = delete($attributes{with_empty});
154 my $empty_title = delete($attributes{empty_title});
156 my $with_optgroups = delete($attributes{with_optgroups});
158 undef $default_key if $default_sub || $default_val_key;
160 my $normalize_entry = sub {
161 my ($type, $entry, $sub, $key) = @_;
163 return $sub->($entry) if $sub;
165 my $ref = ref($entry);
168 return $entry if $type eq 'value' || $type eq 'title';
172 if ( $ref eq 'ARRAY' ) {
173 return $entry->[ $type eq 'value' ? 0 : $type eq 'title' ? 1 : 2 ];
176 return $entry->{$key} if $ref eq 'HASH';
177 return $entry->$key if $type ne 'default' || $entry->can($key);
182 if (defined($default_coll) && !ref $default_coll) {
183 %selected = ($default_coll => 1);
185 } elsif (ref($default_coll) eq 'HASH') {
186 %selected = %{ $default_coll };
188 } elsif ($default_coll) {
189 $default_coll = [ $default_coll ] unless 'ARRAY' eq ref $default_coll;
191 %selected = $default_val_key ? map({ ($normalize_entry->('value', $_, undef, $default_val_key) => 1) } @{ $default_coll })
192 : map({ ($_ => 1) } @{ $default_coll });
195 my $list_to_code = sub {
196 my ($sub_collection) = @_;
198 if ('ARRAY' ne ref $sub_collection) {
199 $sub_collection = [ $sub_collection ];
203 foreach my $entry ( @{ $sub_collection } ) {
207 if ( $value_title_sub ) {
208 ($value, $title) = @{ $value_title_sub->($entry) };
211 $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
212 $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
215 my $default = $default_key ? $normalize_entry->('default', $entry, $default_sub, $default_key) : 0;
217 push(@options, [$value, $title, $selected{$value} || $default]);
220 return join '', map { html_tag('option', escape($_->[1]), value => $_->[0], selected => $_->[2]) } @options;
224 $code .= html_tag('option', escape($empty_title || ''), value => '') if $with_empty;
226 if (!$with_optgroups) {
227 $code .= $list_to_code->($collection);
230 $code .= join '', map {
231 my ($optgroup_title, $sub_collection) = @{ $_ };
232 html_tag('optgroup', $list_to_code->($sub_collection), label => $optgroup_title)
236 my $select_html = html_tag('select', $code, %attributes, name => $name);
241 if (($attributes{style} // '') =~ m{width: *(\d+) *px}i) {
242 $input_style = "width: " . ($1 - 22) . "px";
245 my $input_html = html_tag(
247 autocomplete => 'off',
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,
254 $select_html = html_tag('div', $input_html . $select_html, class => "filtered_select");
260 sub multi_level_select_tag {
261 my ($name, $collection, $levels, %attributes) = @_;
263 _set_id_attribute(\%attributes, $name);
264 my $id = $attributes{id};
266 $collection = [] if defined($collection) && !ref($collection) && ($collection eq '');
268 my $surround_tag = delete($attributes{surround_tag});
270 my $multi_level_select_tag = "";
273 my $base_attributes = delete($attributes{"level_1"}) || {};
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};
282 "value" => $base_value_key,
283 "title" => $base_title_key,
285 $base_attributes->{id} = $id . "_level_1";
286 $base_attributes->{onchange} = $id . "_on_chanche_level_1(this)";
287 my $current_select = select_tag(
293 $multi_level_select_tag .= html_tag($surround_tag, $current_select);
295 $multi_level_select_tag .= $current_select;
300 ($last_object) = grep { $_->{$base_value_key} eq $base_default } @{$collection};
301 } elsif ($base_default_key) {
302 ($last_object) = grep { $_->{$base_default_key} } @{$collection};
304 $last_object = $collection->[0];
306 foreach my $level (2 .. $levels) {
307 my $current_attributes = delete($attributes{"level_$level"}) || {};
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;
317 $level_keys{$level} = {
318 "value" => $current_value_key,
319 "title" => $current_title_key,
320 "object" => $current_object_key,
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(
329 %{$current_attributes}
332 $multi_level_select_tag .= html_tag($surround_tag, $current_select);
334 $multi_level_select_tag .= $current_select;
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};
342 $last_object = $current_collection->[0];
349 my $js_option_string = ""; # js hash map (level->value->{[value: 1, title: 'foo'], ...})
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}}};
363 $js_option_string .= "},";
364 @current_collections = @next_collections;
365 @next_collections = ();
368 $code .= javascript( qq|
369 var ${id}_options = {
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);
384 select_i.disabled = true;
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);
392 select_${next_level}.disabled = false;
393 select_${next_level}.selectedIndex = -1;
399 return $multi_level_select_tag . $code;
403 my ($name, %attributes) = @_;
405 my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
406 delete @attributes{grep { m{^label_} } keys %attributes};
408 _set_id_attribute(\%attributes, $name);
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};
415 if ($attributes{checked}) {
416 $attributes{checked} = 'checked';
418 delete $attributes{checked};
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;
430 sub radio_button_tag {
431 my ($name, %attributes) = @_;
433 my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
434 delete @attributes{grep { m{^label_} } keys %attributes};
436 $attributes{value} = 1 unless exists $attributes{value};
438 _set_id_attribute(\%attributes, $name, 1);
439 my $label = delete $attributes{label};
441 _set_id_attribute(\%attributes, $name . '_' . $attributes{value});
443 if ($attributes{checked}) {
444 $attributes{checked} = 'checked';
446 delete $attributes{checked};
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;
456 my ($onclick, $value, %attributes) = @_;
458 _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
459 $attributes{type} ||= 'button';
460 $attributes{tag} ||= 'input';
462 $onclick = 'if (!confirm("'. _J(delete($attributes{confirm})) .'")) return false; ' . $onclick if $attributes{confirm};
464 if ( $attributes{tag} eq 'input' ) {
465 html_tag('input', undef, %attributes, value => $value, (onclick => $onclick)x!!$onclick)
467 elsif ( $attributes{tag} eq 'button' ) {
468 html_tag('button', undef, %attributes, value => $value, (onclick => $onclick)x!!$onclick);
473 my ($name, $value, %attributes) = @_;
475 _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
477 if ( $attributes{confirm} ) {
478 $attributes{onclick} = 'return confirm("'. _J(delete($attributes{confirm})) .'");';
481 input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
484 sub ajax_submit_tag {
485 my ($url, $form_selector, $text, %attributes) = @_;
488 $form_selector = _J($form_selector);
489 my $onclick = qq|kivi.submit_ajax_form('${url}', '${form_selector}')|;
491 button_tag($onclick, $text, %attributes);
494 sub input_number_tag {
495 my ($name, $value, %params) = @_;
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)) : ();
503 $::request->layout->add_javascripts('kivi.Validator.js');
504 $::request->presenter->need_reinit_widgets($params{id});
507 $name, $::form->format_amount(\%::myconfig, $value, $params{precision}),
508 "data-validate" => "number",
514 sub input_email_tag {
515 my ($name, $value, %params) = @_;
517 my $show_icon = $value || delete($params{show_icon_always});
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;
524 my $html = input_tag_trim($name, $value, %params);
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);
532 return '<span>' . $html . '</span>';
537 html_tag('script', $data, type => 'text/javascript');
540 sub _set_id_attribute {
541 my ($attributes, $name, $unique) = @_;
543 if (!delete($attributes->{no_id}) && !$attributes->{id}) {
544 $attributes->{id} = name_to_id($name);
545 $attributes->{id} .= '_' . $attributes->{value} if $unique;
554 my ($name, $content, %attributes) = @_;
556 _set_id_attribute(\%attributes, $name);
557 $attributes{rows} *= 1; # required by standard
558 $attributes{cols} *= 1; # required by standard
560 if (join_values(class => $attributes{class}) =~ /\btexteditor\b/) {
561 $::request->{layout}->add_javascripts("$_.js") for qw(ckeditor5/ckeditor ckeditor5/translations/de);
564 html_tag('textarea', $content, %attributes, name => $name);
568 my ($href, $content, %params) = @_;
572 html_tag('a', $content, %params, href => $href);
574 # alias for compatibility
575 sub link { goto &link_tag }
578 my ($name, $value, %params) = @_;
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)) : ();
586 $::request->layout->add_javascripts('kivi.Validator.js');
587 $::request->presenter->need_reinit_widgets($params{id});
589 $params{'data-validate'} = join(' ', "date", grep { $_ } (delete $params{'data-validate'}));
592 $name, blessed($value) ? $value->to_lxoffice : $value,
600 my ($content, %params) = @_;
601 return html_tag('div', $content, %params);
609 return html_tag('img', undef, %params);
621 SL::Presenter::Tag - Layouting / tag generation
629 [% P.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
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') %]
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') %]
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' ) %]
649 A module modeled a bit after Rails' ActionView helpers. Several small
650 functions that create HTML tags from various kinds of data sources.
652 The C<id> attribute is usually calculated automatically. This can be
653 overridden by either specifying an C<id> attribute or by setting
658 =head2 LOW-LEVEL FUNCTIONS
662 =item C<html_tag $tag_name, $content_string, %attributes>
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.
669 C<$content_string> is not HTML escaped.
671 =item C<name_to_id $name>
673 Converts a name to a HTML id by replacing various characters.
675 =item C<stringify_attributes %items>
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.
683 =head2 HIGH-LEVEL FUNCTIONS
687 =item C<input_tag $name, $value, %attributes>
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)>.
693 =item C<input_tag_trim $name, $value, %attributes>
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.
699 =item C<input_email_tag $name, $value, %params>
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>
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.
713 =item C<submit_tag $name, $value, %attributes>
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)>.
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.
724 =item C<ajax_submit_tag $url, $form_selector, $text, %attributes>
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>.
732 =item C<button_tag $onclick, $text, %attributes>
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.
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.
743 =item C<man_days_tag $name, $object, %attributes>
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').
748 C<$object> must be a L<Rose::DB::Object> instance using the
749 L<SL::DB::Helper::AttrDuration> helper.
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.
758 [% P.man_days_tag("requirement_spec_item.time_estimation", SELF.item) %]
760 The attribute C<size> can be used to set the text input's size. It
763 =item C<hidden_tag $name, $value, %attributes>
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)>.
769 =item C<checkbox_tag $name, %attributes>
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>.
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.
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.
786 =item C<radio_button_tag $name, %attributes>
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)>.
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.
799 =item C<select_tag $name, \@collection, %attributes>
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:
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.
813 =item 2. A scalar. The scalar is both the value and the title.
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.
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
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).
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.
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.
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
845 The tag's C<id> defaults to C<name_to_id($name)>.
847 The option C<default> can be quite a lot of things:
851 =item 1. A scalar value. This is the value of the entry that's
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.
857 =item 3. An array reference containing scalar values. Same as 1., just
858 for the case of C<multiple=1>.
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.
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.
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).
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:
883 =item 1. Array of array references. Each element in the
884 C<\@collection> is converted into an optgroup.
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.
892 Example for use of optgroups:
894 # First in a controller:
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" },
901 [ t8("Another optgroup, with a lot of items from Rose"),
902 SL::DB::Manager::Customer->get_all_sorted ],
905 # Later in the template:
906 [% L.select_tag('the_selection', COLLECTION, with_optgroups=1, title_key='name') %]
908 =item C<multi_level_select_tag $name, \@collection, $levels, %attributes>
910 Creates multiple HTML 'select' tags, one for each level. Each tag ist created
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.
917 The following are used directly from each level:
921 =item I<name> is deleted from level attributes.
922 The I<name> is used for the name of the 'select' tag.
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.
929 =item I<id> is overridden.
930 The I<id> is set to a specific value for use in JavaScript.
932 =item I<value_key> names the key for the value of level object. Defaults to
935 =item I<title_key> names the key for the titel of level object. Defaults to
936 C<$attributes{value_key}>.
938 =item I<default> is used to find the default level object.
940 =item I<default_key> names the key for the default feld of level object. Is
941 ignored if I<default> is set.
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.
949 A I<surround_tag> can be specified, which generates an C<html_tag> with the
950 corresponding tag around every C<select_tag>.
954 # First in a controller:
956 { 'description' => 'foo_1', 'id' => '1',
958 { 'id' => 3, 'description' => "bar_1",
960 { 'id' => 1, 'value' => "foobar_1", },
961 { 'id' => 2, 'value' => "foobar_2", },
963 { 'id' => 4, 'description' => "bar_2",
966 { 'description' => 'foo_2', 'id' => '2',
968 { 'id' => 1, 'description' => "bar_1",
970 { 'id' => 3, 'value' => "foobar_3", },
971 { 'id' => 4, 'value' => "foobar_4", },
973 { 'id' => 2, 'description' => "bar_2",
975 { 'id' => 5, 'value' => "test_5", },
980 # Later in the template (in a table):
981 [% L.multi_level_select_tag("multi_select_foo_bar_foobar", COLLECTION, 3,
986 title_key="description",
993 title_key="description",
1013 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
1014 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>