1 package SL::Template::Plugin::L;
 
   3 use base qw( Template::Plugin );
 
   5 use List::MoreUtils qw(apply);
 
   6 use List::Util qw(max);
 
  10 { # This will give you an id for identifying html tags and such.
 
  11   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
 
  12   # Do not use these id's to store information across requests.
 
  13 my $_id_sequence = int rand 1e7;
 
  15   return $_id_sequence = ($_id_sequence + 1) % 1e7;
 
  21   return $::locale->quote_special_chars('HTML', $string);
 
  25   my $string =  "" . shift;
 
  26   $string    =~ s/\"/\\\"/g;
 
  31   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
 
  35   my ($class, $context, @args) = @_;
 
  43   die 'not an accessor' if @_ > 1;
 
  44   return $_[0]->{CONTEXT};
 
  51   $name    =~ s/[^\w_]/_/g;
 
  58   my ($self, @slurp)    = @_;
 
  59   my %options = _hashify(@slurp);
 
  62   while (my ($name, $value) = each %options) {
 
  64     next if $name eq 'disabled' && !$value;
 
  65     $value = '' if !defined($value);
 
  66     push @result, _H($name) . '="' . _H($value) . '"';
 
  69   return @result ? ' ' . join(' ', @result) : '';
 
  73   my ($self, $tag, $content, @slurp) = @_;
 
  74   my $attributes = $self->attributes(@slurp);
 
  76   return "<${tag}${attributes}/>" unless defined($content);
 
  77   return "<${tag}${attributes}>${content}</${tag}>";
 
  83   my $options_str     = shift;
 
  84   my %attributes      = _hashify(@_);
 
  86   $attributes{id}   ||= $self->name_to_id($name);
 
  87   $options_str        = $self->options_for_select($options_str) if ref $options_str;
 
  89   return $self->html_tag('select', $options_str, %attributes, name => $name);
 
  93   my ($self, $name, $content, @slurp) = @_;
 
  94   my %attributes      = _hashify(@slurp);
 
  96   $attributes{id}   ||= $self->name_to_id($name);
 
  97   $content            = $content ? _H($content) : '';
 
  99   return $self->html_tag('textarea', $content, %attributes, name => $name);
 
 103   my ($self, $name, @slurp) = @_;
 
 104   my %attributes       = _hashify(@slurp);
 
 106   $attributes{id}    ||= $self->name_to_id($name);
 
 107   $attributes{value}   = 1 unless defined $attributes{value};
 
 108   my $label            = delete $attributes{label};
 
 109   my $checkall         = delete $attributes{checkall};
 
 111   if ($attributes{checked}) {
 
 112     $attributes{checked} = 'checked';
 
 114     delete $attributes{checked};
 
 117   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
 
 118   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
 
 119   $code    .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
 
 124 sub radio_button_tag {
 
 127   my %attributes       = _hashify(@_);
 
 129   $attributes{value}   = 1 unless defined $attributes{value};
 
 130   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
 
 131   my $label            = delete $attributes{label};
 
 133   if ($attributes{checked}) {
 
 134     $attributes{checked} = 'checked';
 
 136     delete $attributes{checked};
 
 139   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
 
 140   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
 
 146   my ($self, $name, $value, @slurp) = @_;
 
 147   my %attributes      = _hashify(@slurp);
 
 149   $attributes{id}   ||= $self->name_to_id($name);
 
 150   $attributes{type} ||= 'text';
 
 152   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
 
 156   return shift->input_tag(@_, type => 'hidden');
 
 160   my ($self, $content, @slurp) = @_;
 
 161   return $self->html_tag('div', $content, @slurp);
 
 165   my ($self, $content, @slurp) = @_;
 
 166   return $self->html_tag('ul', $content, @slurp);
 
 170   my ($self, $content, @slurp) = @_;
 
 171   return $self->html_tag('li', $content, @slurp);
 
 175   my ($self, $href, $content, @slurp) = @_;
 
 176   my %params = _hashify(@slurp);
 
 180   return $self->html_tag('a', $content, %params, href => $href);
 
 184   my ($self, $name, $value, @slurp) = @_;
 
 185   my %attributes = _hashify(@slurp);
 
 187   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
 
 189   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
 
 193   my ($self, $onclick, $value, @slurp) = @_;
 
 194   my %attributes = _hashify(@slurp);
 
 196   return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
 
 199 sub options_for_select {
 
 201   my $collection      = shift;
 
 202   my %options         = _hashify(@_);
 
 204   my $value_key       = $options{value} || 'id';
 
 205   my $title_key       = $options{title} || $value_key;
 
 207   my $value_sub       = $options{value_sub};
 
 208   my $title_sub       = $options{title_sub};
 
 210   my $value_title_sub = $options{value_title_sub};
 
 212   my %selected        = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
 
 215     my ($element, $index, $key, $sub) = @_;
 
 216     my $ref = ref $element;
 
 217     return  $sub            ? $sub->($element)
 
 219          :  $ref eq 'ARRAY' ? $element->[$index]
 
 220          :  $ref eq 'HASH'  ? $element->{$key}
 
 225   push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
 
 226   push @elements, map [
 
 227     $value_title_sub ? @{ $value_title_sub->($_) } : (
 
 228       $access->($_, 0, $value_key, $value_sub),
 
 229       $access->($_, 1, $title_key, $title_sub),
 
 231   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
 
 234   foreach my $result (@elements) {
 
 235     my %attributes = ( value => $result->[0] );
 
 236     $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
 
 238     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
 
 245   my ($self, $data) = @_;
 
 246   return $self->html_tag('script', $data, type => 'text/javascript');
 
 253   foreach my $file (@_) {
 
 254     $file .= '.css'        unless $file =~ m/\.css$/;
 
 255     $file  = "css/${file}" unless $file =~ m|/|;
 
 257     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
 
 264   my ($self, $name, $value, @slurp) = @_;
 
 265   my %params   = _hashify(@slurp);
 
 266   my $name_e   = _H($name);
 
 268   my $datefmt  = apply {
 
 272   } $::myconfig{"dateformat"};
 
 274   $params{cal_align} ||= 'BR';
 
 276   $self->input_tag($name, $value,
 
 279     title  => _H($::myconfig{dateformat}),
 
 280     onBlur => 'check_right_date_format(this)',
 
 282   ) . ((!$params{no_cal}) ?
 
 283   $self->html_tag('img', undef,
 
 284     src    => 'image/calendar.png',
 
 286     title  => _H($::myconfig{dateformat}),
 
 290     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
 
 298   foreach my $file (@_) {
 
 299     $file .= '.js'        unless $file =~ m/\.js$/;
 
 300     $file  = "js/${file}" unless $file =~ m|/|;
 
 302     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
 
 309   my ($self, $tabs, @slurp) = @_;
 
 310   my %params   = _hashify(@slurp);
 
 311   my $id       = $params{id} || 'tab_' . _tag_id();
 
 313   $params{selected} *= 1;
 
 315   die 'L.tabbed needs an arrayred of tabs for first argument'
 
 316     unless ref $tabs eq 'ARRAY';
 
 318   my (@header, @blocks);
 
 319   for my $i (0..$#$tabs) {
 
 320     my $tab = $tabs->[$i];
 
 324     my $selected = $params{selected} == $i;
 
 325     my $tab_id   = "__tab_id_$i";
 
 326     push @header, $self->li_tag(
 
 327       $self->link('', $tab->{name}, rel => $tab_id),
 
 328         ($selected ? (class => 'selected') : ())
 
 330     push @blocks, $self->div_tag($tab->{data},
 
 331       id => $tab_id, class => 'tabcontent');
 
 334   return '' unless @header;
 
 335   return $self->ul_tag(
 
 336     join('', @header), id => $id, class => 'shadetabs'
 
 339     join('', @blocks), class => 'tabcontentstyle'
 
 342     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
 
 343     qq|$id.setselectedClassTarget("link");$id.init();|
 
 348   my ($self, $name, $src, @slurp) = @_;
 
 349   my %params = _hashify(@slurp);
 
 351   $params{method} ||= 'process';
 
 353   return () if defined $params{if} && !$params{if};
 
 356   if ($params{method} eq 'raw') {
 
 358   } elsif ($params{method} eq 'process') {
 
 359     $data = $self->_context->process($src, %{ $params{args} || {} });
 
 361     die "unknown tag method '$params{method}'";
 
 364   return () unless $data;
 
 366   return +{ name => $name, data => $data };
 
 370   my ($self, $name, $value, @slurp) = @_;
 
 371   my %attributes      = _hashify(@slurp);
 
 373   my $rows = delete $attributes{rows}     || 1;
 
 374   my $min  = delete $attributes{min_rows} || 1;
 
 377     ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
 
 378     : $self->input_tag($name, $value, %attributes);
 
 381 sub multiselect2side {
 
 382   my ($self, $id, @slurp) = @_;
 
 383   my %params              = _hashify(@slurp);
 
 385   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
 
 386   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
 
 387   $params{moveOptions}    = 'false';
 
 389   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
 
 391 <script type="text/javascript">
 
 392   \$().ready(function() {
 
 393     \$('#${id}').multiselect2side({ ${vars} });
 
 401 sub sortable_element {
 
 402   my ($self, $selector, @slurp) = @_;
 
 403   my %params                    = _hashify(@slurp);
 
 405   my %attributes = ( distance => 5,
 
 406                      helper   => <<'JAVASCRIPT' );
 
 407     function(event, ui) {
 
 408       ui.children().each(function() {
 
 409         $(this).width($(this).width());
 
 417   if ($params{url} && $params{with}) {
 
 418     my $as      = $params{as} || $params{with};
 
 419     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
 
 420     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
 
 422     $stop_event = <<JAVASCRIPT;
 
 423         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
 
 427   if (!$params{dont_recolor}) {
 
 428     $stop_event .= <<JAVASCRIPT;
 
 429         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
 
 430         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
 
 435     $attributes{stop} = <<JAVASCRIPT;
 
 436       function(event, ui) {
 
 443   $params{handle}     = '.dragdrop' unless exists $params{handle};
 
 444   $attributes{handle} = "'$params{handle}'" if $params{handle};
 
 446   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
 
 448   my $code = <<JAVASCRIPT;
 
 449 <script type="text/javascript">
 
 451     \$( "${selector}" ).sortable({ ${attr_str} })
 
 459 sub online_help_tag {
 
 460   my ($self, $tag, @slurp) = @_;
 
 461   my %params               = _hashify(@slurp);
 
 462   my $cc                   = $::myconfig{countrycode};
 
 463   my $file                 = "doc/online/$cc/$tag.html";
 
 464   my $text                 = $params{text} || $::locale->text('Help');
 
 466   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
 
 467   return unless -f $file;
 
 468   return $self->html_tag('a', $text, href => $file, target => '_blank');
 
 473   require Data::Dumper;
 
 474   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
 
 483 SL::Templates::Plugin::L -- Layouting / tag generation
 
 487 Usage from a template:
 
 491   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
 
 493   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
 
 494                                                       { direction => 'right', display => 'To the right' } ],
 
 495                                                     value => 'direction', title => 'display', default => 'right')) %]
 
 499 A module modeled a bit after Rails' ActionView helpers. Several small
 
 500 functions that create HTML tags from various kinds of data sources.
 
 504 =head2 LOW-LEVEL FUNCTIONS
 
 508 =item C<name_to_id $name>
 
 510 Converts a name to a HTML id by replacing various characters.
 
 512 =item C<attributes %items>
 
 514 Creates a string from all elements in C<%items> suitable for usage as
 
 515 HTML tag attributes. Keys and values are HTML escaped even though keys
 
 516 must not contain non-ASCII characters for browsers to accept them.
 
 518 =item C<html_tag $tag_name, $content_string, %attributes>
 
 520 Creates an opening and closing HTML tag for C<$tag_name> and puts
 
 521 C<$content_string> between the two. If C<$content_string> is undefined
 
 522 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
 
 523 are key/value pairs added to the opening tag.
 
 525 C<$content_string> is not HTML escaped.
 
 529 =head2 HIGH-LEVEL FUNCTIONS
 
 533 =item C<select_tag $name, $options_string, %attributes>
 
 535 Creates a HTML 'select' tag named C<$name> with the contents
 
 536 C<$options_string> and with arbitrary HTML attributes from
 
 537 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
 
 539 The C<$options_string> is usually created by the
 
 540 L</options_for_select> function. If C<$options_string> is an array
 
 541 reference then it will be passed to L</options_for_select>
 
 544 =item C<input_tag $name, $value, %attributes>
 
 546 Creates a HTML 'input type=text' tag named C<$name> with the value
 
 547 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 548 tag's C<id> defaults to C<name_to_id($name)>.
 
 550 =item C<hidden_tag $name, $value, %attributes>
 
 552 Creates a HTML 'input type=hidden' tag named C<$name> with the value
 
 553 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 554 tag's C<id> defaults to C<name_to_id($name)>.
 
 556 =item C<submit_tag $name, $value, %attributes>
 
 558 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
 
 559 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 560 tag's C<id> defaults to C<name_to_id($name)>.
 
 562 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
 
 563 be added via the C<onclick> handler asking the question given with
 
 564 C<$attributes{confirm}>. If request is only submitted if the user
 
 565 clicks the dialog's ok/yes button.
 
 567 =item C<textarea_tag $name, $value, %attributes>
 
 569 Creates a HTML 'textarea' tag named C<$name> with the content
 
 570 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 571 tag's C<id> defaults to C<name_to_id($name)>.
 
 573 =item C<checkbox_tag $name, %attributes>
 
 575 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
 
 576 HTML attributes from C<%attributes>. The tag's C<id> defaults to
 
 577 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
 
 579 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 580 created with said C<label>. No attribute named C<label> is created in
 
 583 If C<%attributes> contains a key C<checkall> then the value is taken as a
 
 584 JQuery selector and clicking this checkbox will also toggle all checkboxes
 
 585 matching the selector.
 
 587 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 589 Creates a date input field, with an attached javascript that will open a
 
 590 calendar on click. The javascript ist by default anchoered at the bottom right
 
 591 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 592 the details, usually you'll want a two letter abbreviation of the alignment.
 
 593 Right + Bottom becomes C<BL>.
 
 595 =item C<radio_button_tag $name, %attributes>
 
 597 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
 
 598 HTML attributes from C<%attributes>. The tag's C<value> defaults to
 
 599 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
 
 601 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 602 created with said C<label>. No attribute named C<label> is created in
 
 605 =item C<javascript_tag $file1, $file2, $file3...>
 
 607 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
 
 608 tag for each file name parameter passed. Each file name will be
 
 609 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
 
 610 doesn't contain a slash.
 
 612 =item C<stylesheet_tag $file1, $file2, $file3...>
 
 614 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
 
 615 for each file name parameter passed. Each file name will be postfixed
 
 616 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
 
 619 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 621 Creates a date input field, with an attached javascript that will open a
 
 622 calendar on click. The javascript ist by default anchoered at the bottom right
 
 623 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 624 the details, usually you'll want a two letter abbreviation of the alignment.
 
 625 Right + Bottom becomes C<BL>.
 
 627 =item C<tabbed \@tab, %attributes>
 
 629 Will create a tabbed area. The tabs should be created with the helper function
 
 633     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
 
 634     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
 
 637 An optional attribute is C<selected>, which accepts the ordinal of a tab which
 
 638 should be selected by default.
 
 640 =item C<areainput_tag $name, $content, %PARAMS>
 
 642 Creates a generic input tag or textarea tag, depending on content size. The
 
 643 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
 
 644 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
 
 646 You can force input by setting rows to 1, and you can force textarea by setting
 
 649 =item C<multiselect2side $id, %params>
 
 651 Creates a JavaScript snippet calling the jQuery function
 
 652 C<multiselect2side> on the select control with the ID C<$id>. The
 
 653 select itself is not created. C<%params> can contain the following
 
 660 The label of the list of available options. Defaults to the
 
 661 translation of 'Available'.
 
 665 The label of the list of selected options. Defaults to the
 
 666 translation of 'Selected'.
 
 670 =item C<sortable_element $selector, %params>
 
 672 Makes the children of the DOM element C<$selector> (a jQuery selector)
 
 673 sortable with the I<jQuery UI Selectable> library. The children can be
 
 674 dragged & dropped around. After dropping an element an URL can be
 
 675 postet to with the element IDs of the sorted children.
 
 677 If this is used then the JavaScript file C<js/jquery-ui.js> must be
 
 678 included manually as well as it isn't loaded via C<$::form-gt;header>.
 
 680 C<%params> can contain the following entries:
 
 686 The URL to POST an AJAX request to after a dragged element has been
 
 687 dropped. The AJAX request's return value is ignored. If given then
 
 688 C<$params{with}> must be given as well.
 
 692 A string that is interpreted as the prefix of the children's ID. Upon
 
 693 POSTing the result each child whose ID starts with C<$params{with}> is
 
 694 considered. The prefix and the following "_" is removed from the
 
 695 ID. The remaining parts of the IDs of those children are posted as a
 
 696 single array parameter. The array parameter's name is either
 
 697 C<$params{as}> or, missing that, C<$params{with}>.
 
 701 Sets the POST parameter name for AJAX request after dropping an
 
 702 element (see C<$params{with}>).
 
 706 An optional jQuery selector specifying which part of the child element
 
 707 is dragable. If the parameter is not given then it defaults to
 
 708 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
 
 709 parameter is set and empty then the whole child element is dragable,
 
 710 and clicks through to underlying elements like inputs or links might
 
 713 =item C<dont_recolor>
 
 715 If trueish then the children will not be recolored. The default is to
 
 716 recolor the children by setting the class C<listrow0> on odd and
 
 717 C<listrow1> on even entries.
 
 723   <script type="text/javascript" src="js/jquery-ui.js"></script>
 
 725   <table id="thing_list">
 
 727       <tr><td>This</td><td>That</td></tr>
 
 730       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
 
 731       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
 
 732       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
 
 736   [% L.sortable_element('#thing_list tbody',
 
 737                         url          => 'controller.pl?action=SystemThings/reorder',
 
 740                         recolor_rows => 1) %]
 
 742 After dropping e.g. the third element at the top of the list a POST
 
 743 request would be made to the C<reorder> action of the C<SystemThings>
 
 744 controller with a single parameter called C<thing_ids> -- an array
 
 745 containing the values C<[ 6, 2, 15 ]>.
 
 749 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
 
 753 =head2 CONVERSION FUNCTIONS
 
 757 =item C<options_for_select \@collection, %options>
 
 759 Creates a string suitable for a HTML 'select' tag consisting of one
 
 760 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
 
 761 to use and the title to display are extracted from the elements in
 
 762 C<\@collection>. Each element can be one of four things:
 
 766 =item 1. An array reference with at least two elements. The first element is
 
 767 the value, the second element is its title.
 
 769 =item 2. A scalar. The scalar is both the value and the title.
 
 771 =item 3. A hash reference. In this case C<%options> must contain
 
 772 I<value> and I<title> keys that name the keys in the element to use
 
 773 for the value and title respectively.
 
 775 =item 4. A blessed reference. In this case C<%options> must contain
 
 776 I<value> and I<title> keys that name functions called on the blessed
 
 777 reference whose return values are used as the value and title
 
 782 For cases 3 and 4 C<$options{value}> defaults to C<id> and
 
 783 C<$options{title}> defaults to C<$options{value}>.
 
 785 In addition to pure keys/method you can also provide coderefs as I<value_sub>
 
 786 and/or I<title_sub>. If present, these take precedence over keys or methods,
 
 787 and are called with the element as first argument. It must return the value or
 
 790 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
 
 791 precedence over each individual sub. It will only be called once for each
 
 792 element and must return a list of value and title.
 
 794 If the option C<with_empty> is set then an empty element (value
 
 795 C<undef>) will be used as the first element. The title to display for
 
 796 this element can be set with the option C<empty_title> and defaults to
 
 799 The option C<default> can be either a scalar or an array reference
 
 800 containing the values of the options which should be set to be
 
 803 =item C<tab, description, target, %PARAMS>
 
 805 Creates a tab for C<tabbed>. The description will be used as displayed name.
 
 806 The target should be a block or template that can be processed. C<tab> supports
 
 807 a C<method> parameter, which can override the process method to apply target.
 
 808 C<method => 'raw'> will just include the given text as is. I was too lazy to
 
 809 implement C<include> properly.
 
 811 Also an C<if> attribute is supported, so that tabs can be suppressed based on
 
 812 some occasion. In this case the supplied block won't even get processed, and
 
 813 the resulting tab will get ignored by C<tabbed>:
 
 815   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
 
 819 =head1 MODULE AUTHORS
 
 821 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 823 L<http://linet-services.de>