1 package SL::Template::Plugin::L;
3 use base qw( Template::Plugin );
5 use List::MoreUtils qw(apply);
6 use List::Util qw(max);
7 use Scalar::Util qw(blessed);
11 { # This will give you an id for identifying html tags and such.
12 # It's guaranteed to be unique unless you exceed 10 mio calls per request.
13 # Do not use these id's to store information across requests.
14 my $_id_sequence = int rand 1e7;
16 return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
20 my %_valueless_attributes = map { $_ => 1 } qw(
21 checked compact declare defer disabled ismap multiple noresize noshade nowrap
27 return $::locale->quote_special_chars('HTML', $string);
31 my $string = "" . shift;
32 $string =~ s/\"/\\\"/g;
37 return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
41 my ($class, $context, @args) = @_;
49 die 'not an accessor' if @_ > 1;
50 return $_[0]->{CONTEXT};
57 $name =~ s/[^\w_]/_/g;
64 my ($self, @slurp) = @_;
65 my %options = _hashify(@slurp);
68 while (my ($name, $value) = each %options) {
70 next if $_valueless_attributes{$name} && !$value;
71 $value = '' if !defined($value);
72 push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
75 return @result ? ' ' . join(' ', @result) : '';
79 my ($self, $tag, $content, @slurp) = @_;
80 my $attributes = $self->attributes(@slurp);
82 return "<${tag}${attributes}>" unless defined($content);
83 return "<${tag}${attributes}>${content}</${tag}>";
89 my $options_str = shift;
90 my %attributes = _hashify(@_);
92 $attributes{id} ||= $self->name_to_id($name);
93 $options_str = $self->options_for_select($options_str) if ref $options_str;
95 return $self->html_tag('select', $options_str, %attributes, name => $name);
99 my ($self, $name, $content, @slurp) = @_;
100 my %attributes = _hashify(@slurp);
102 $attributes{id} ||= $self->name_to_id($name);
103 $attributes{rows} *= 1; # required by standard
104 $attributes{cols} *= 1; # required by standard
105 $content = $content ? _H($content) : '';
107 return $self->html_tag('textarea', $content, %attributes, name => $name);
111 my ($self, $name, @slurp) = @_;
112 my %attributes = _hashify(@slurp);
114 $attributes{id} ||= $self->name_to_id($name);
115 $attributes{value} = 1 unless defined $attributes{value};
116 my $label = delete $attributes{label};
117 my $checkall = delete $attributes{checkall};
119 if ($attributes{checked}) {
120 $attributes{checked} = 'checked';
122 delete $attributes{checked};
125 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
126 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
127 $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
132 sub radio_button_tag {
135 my %attributes = _hashify(@_);
137 $attributes{value} = 1 unless defined $attributes{value};
138 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
139 my $label = delete $attributes{label};
141 if ($attributes{checked}) {
142 $attributes{checked} = 'checked';
144 delete $attributes{checked};
147 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
148 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
154 my ($self, $name, $value, @slurp) = @_;
155 my %attributes = _hashify(@slurp);
157 $attributes{id} ||= $self->name_to_id($name);
158 $attributes{type} ||= 'text';
160 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
164 return shift->input_tag(@_, type => 'hidden');
168 my ($self, $content, @slurp) = @_;
169 return $self->html_tag('div', $content, @slurp);
173 my ($self, $content, @slurp) = @_;
174 return $self->html_tag('ul', $content, @slurp);
178 my ($self, $content, @slurp) = @_;
179 return $self->html_tag('li', $content, @slurp);
183 my ($self, $href, $content, @slurp) = @_;
184 my %params = _hashify(@slurp);
188 return $self->html_tag('a', $content, %params, href => $href);
192 my ($self, $name, $value, @slurp) = @_;
193 my %attributes = _hashify(@slurp);
195 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
197 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
201 my ($self, $onclick, $value, @slurp) = @_;
202 my %attributes = _hashify(@slurp);
204 $attributes{id} ||= $self->name_to_id($attributes{name}) if $attributes{name};
205 $attributes{type} ||= 'button';
207 return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
210 sub options_for_select {
212 my $collection = shift;
213 my %options = _hashify(@_);
215 my $value_key = $options{value} || 'id';
216 my $title_key = $options{title} || $value_key;
218 my $value_sub = $options{value_sub};
219 my $title_sub = $options{title_sub};
221 my $value_title_sub = $options{value_title_sub};
223 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
226 my ($element, $index, $key, $sub) = @_;
227 my $ref = ref $element;
228 return $sub ? $sub->($element)
230 : $ref eq 'ARRAY' ? $element->[$index]
231 : $ref eq 'HASH' ? $element->{$key}
236 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
237 push @elements, map [
238 $value_title_sub ? @{ $value_title_sub->($_) } : (
239 $access->($_, 0, $value_key, $value_sub),
240 $access->($_, 1, $title_key, $title_sub),
242 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
245 foreach my $result (@elements) {
246 my %attributes = ( value => $result->[0] );
247 $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
249 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
256 my ($self, $data) = @_;
257 return $self->html_tag('script', $data, type => 'text/javascript');
264 foreach my $file (@_) {
265 $file .= '.css' unless $file =~ m/\.css$/;
266 $file = "css/${file}" unless $file =~ m|/|;
268 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
275 my ($self, $name, $value, @slurp) = @_;
276 my %params = _hashify(@slurp);
277 my $name_e = _H($name);
279 my $datefmt = apply {
283 } $::myconfig{"dateformat"};
285 my $cal_align = delete $params{cal_align} || 'BR';
286 my $onchange = delete $params{onchange};
287 my $str_value = blessed $value ? $value->to_lxoffice : $value;
289 $self->input_tag($name, $str_value,
292 title => _H($::myconfig{dateformat}),
293 onBlur => 'check_right_date_format(this)',
295 onChange => $onchange,
298 ) . ((!$params{no_cal} && !$params{readonly}) ?
299 $self->html_tag('img', undef,
300 src => 'image/calendar.png',
301 alt => $::locale->text('Calendar'),
303 title => _H($::myconfig{dateformat}),
307 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
311 sub customer_picker {
312 my ($self, $name, $value, %params) = @_;
313 my $name_e = _H($name);
315 $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
316 $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
317 $self->javascript(<<JS);
318 function autocomplete_customer (selector, column) {
319 \$(function(){ \$(selector).autocomplete({
320 source: function(req, rsp) {
322 url: 'controller.pl?action=Customer/ajax_autocomplete',
327 current: function() { \$('#$name_e').val() },
330 success: function (data){ rsp(data) }
335 select: function(event, ui) {
336 \$('#$name_e').val(ui.item.id);
337 \$('#$name_e\_name').val(ui.item.name);
341 autocomplete_customer('#$name_e\_name');
345 # simple version with select_tag
346 sub vendor_selector {
347 my ($self, $name, $value, %params) = @_;
349 my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
350 (ref $value && $value->can('id')) ? $value->id : '';
351 my $options_str = $self->options_for_select(SL::DB::Manager::Vendor->get_all(),
352 default => $actual_vendor_id,
353 title_sub => sub { $_[0]->vendornumber . " : " . $_[0]->name },
356 return $self->select_tag($name, $options_str, %params);
360 # simple version with select_tag
362 my ($self, $name, $value, %params) = @_;
364 my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
365 (ref $value && $value->can('id')) ? $value->id : '';
366 my $options_str = $self->options_for_select(SL::DB::Manager::Part->get_all(),
367 default => $actual_part_id,
368 title_sub => sub { $_[0]->partnumber . " : " . $_[0]->description },
371 return $self->select_tag($name, $options_str, %params);
379 foreach my $file (@_) {
380 $file .= '.js' unless $file =~ m/\.js$/;
381 $file = "js/${file}" unless $file =~ m|/|;
383 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
390 my ($self, $tabs, @slurp) = @_;
391 my %params = _hashify(@slurp);
392 my $id = $params{id} || 'tab_' . _tag_id();
394 $params{selected} *= 1;
396 die 'L.tabbed needs an arrayred of tabs for first argument'
397 unless ref $tabs eq 'ARRAY';
399 my (@header, @blocks);
400 for my $i (0..$#$tabs) {
401 my $tab = $tabs->[$i];
405 my $selected = $params{selected} == $i;
406 my $tab_id = "__tab_id_$i";
407 push @header, $self->li_tag(
408 $self->link('', $tab->{name}, rel => $tab_id),
409 ($selected ? (class => 'selected') : ())
411 push @blocks, $self->div_tag($tab->{data},
412 id => $tab_id, class => 'tabcontent');
415 return '' unless @header;
416 return $self->ul_tag(
417 join('', @header), id => $id, class => 'shadetabs'
420 join('', @blocks), class => 'tabcontentstyle'
423 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
424 qq|$id.setselectedClassTarget("link");$id.init();|
429 my ($self, $name, $src, @slurp) = @_;
430 my %params = _hashify(@slurp);
432 $params{method} ||= 'process';
434 return () if defined $params{if} && !$params{if};
437 if ($params{method} eq 'raw') {
439 } elsif ($params{method} eq 'process') {
440 $data = $self->_context->process($src, %{ $params{args} || {} });
442 die "unknown tag method '$params{method}'";
445 return () unless $data;
447 return +{ name => $name, data => $data };
451 my ($self, $name, $value, @slurp) = @_;
452 my %attributes = _hashify(@slurp);
455 my $min = delete $attributes{min_rows} || 1;
457 if (exists $attributes{cols}) {
458 $cols = delete $attributes{cols};
459 $rows = $::form->numtextrows($value, $cols);
461 $rows = delete $attributes{rows} || 1;
465 ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
466 : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
469 sub multiselect2side {
470 my ($self, $id, @slurp) = @_;
471 my %params = _hashify(@slurp);
473 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
474 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
475 $params{moveOptions} = 'false';
477 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
479 <script type="text/javascript">
480 \$().ready(function() {
481 \$('#${id}').multiselect2side({ ${vars} });
489 sub sortable_element {
490 my ($self, $selector, @slurp) = @_;
491 my %params = _hashify(@slurp);
493 my %attributes = ( distance => 5,
494 helper => <<'JAVASCRIPT' );
495 function(event, ui) {
496 ui.children().each(function() {
497 $(this).width($(this).width());
505 if ($params{url} && $params{with}) {
506 my $as = $params{as} || $params{with};
507 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
508 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
510 $stop_event = <<JAVASCRIPT;
511 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
515 if (!$params{dont_recolor}) {
516 $stop_event .= <<JAVASCRIPT;
517 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
518 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
523 $attributes{stop} = <<JAVASCRIPT;
524 function(event, ui) {
531 $params{handle} = '.dragdrop' unless exists $params{handle};
532 $attributes{handle} = "'$params{handle}'" if $params{handle};
534 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
536 my $code = <<JAVASCRIPT;
537 <script type="text/javascript">
539 \$( "${selector}" ).sortable({ ${attr_str} })
547 sub online_help_tag {
548 my ($self, $tag, @slurp) = @_;
549 my %params = _hashify(@slurp);
550 my $cc = $::myconfig{countrycode};
551 my $file = "doc/online/$cc/$tag.html";
552 my $text = $params{text} || $::locale->text('Help');
554 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
555 return unless -f $file;
556 return $self->html_tag('a', $text, href => $file, class => 'jqModal')
561 require Data::Dumper;
562 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
571 SL::Templates::Plugin::L -- Layouting / tag generation
575 Usage from a template:
579 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
581 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
582 { direction => 'right', display => 'To the right' } ],
583 value => 'direction', title => 'display', default => 'right')) %]
587 A module modeled a bit after Rails' ActionView helpers. Several small
588 functions that create HTML tags from various kinds of data sources.
592 =head2 LOW-LEVEL FUNCTIONS
596 =item C<name_to_id $name>
598 Converts a name to a HTML id by replacing various characters.
600 =item C<attributes %items>
602 Creates a string from all elements in C<%items> suitable for usage as
603 HTML tag attributes. Keys and values are HTML escaped even though keys
604 must not contain non-ASCII characters for browsers to accept them.
606 =item C<html_tag $tag_name, $content_string, %attributes>
608 Creates an opening and closing HTML tag for C<$tag_name> and puts
609 C<$content_string> between the two. If C<$content_string> is undefined
610 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
611 are key/value pairs added to the opening tag.
613 C<$content_string> is not HTML escaped.
617 =head2 HIGH-LEVEL FUNCTIONS
621 =item C<select_tag $name, $options_string, %attributes>
623 Creates a HTML 'select' tag named C<$name> with the contents
624 C<$options_string> and with arbitrary HTML attributes from
625 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
627 The C<$options_string> is usually created by the
628 L</options_for_select> function. If C<$options_string> is an array
629 reference then it will be passed to L</options_for_select>
632 =item C<input_tag $name, $value, %attributes>
634 Creates a HTML 'input type=text' tag named C<$name> with the value
635 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
636 tag's C<id> defaults to C<name_to_id($name)>.
638 =item C<hidden_tag $name, $value, %attributes>
640 Creates a HTML 'input type=hidden' tag named C<$name> with the value
641 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
642 tag's C<id> defaults to C<name_to_id($name)>.
644 =item C<submit_tag $name, $value, %attributes>
646 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
647 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
648 tag's C<id> defaults to C<name_to_id($name)>.
650 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
651 be added via the C<onclick> handler asking the question given with
652 C<$attributes{confirm}>. If request is only submitted if the user
653 clicks the dialog's ok/yes button.
655 =item C<textarea_tag $name, $value, %attributes>
657 Creates a HTML 'textarea' tag named C<$name> with the content
658 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
659 tag's C<id> defaults to C<name_to_id($name)>.
661 =item C<checkbox_tag $name, %attributes>
663 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
664 HTML attributes from C<%attributes>. The tag's C<id> defaults to
665 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
667 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
668 created with said C<label>. No attribute named C<label> is created in
671 If C<%attributes> contains a key C<checkall> then the value is taken as a
672 JQuery selector and clicking this checkbox will also toggle all checkboxes
673 matching the selector.
675 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
677 Creates a date input field, with an attached javascript that will open a
678 calendar on click. The javascript ist by default anchoered at the bottom right
679 sight. This can be overridden with C<cal_align>, see Calendar documentation for
680 the details, usually you'll want a two letter abbreviation of the alignment.
681 Right + Bottom becomes C<BL>.
683 =item C<radio_button_tag $name, %attributes>
685 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
686 HTML attributes from C<%attributes>. The tag's C<value> defaults to
687 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
689 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
690 created with said C<label>. No attribute named C<label> is created in
693 =item C<javascript_tag $file1, $file2, $file3...>
695 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
696 tag for each file name parameter passed. Each file name will be
697 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
698 doesn't contain a slash.
700 =item C<stylesheet_tag $file1, $file2, $file3...>
702 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
703 for each file name parameter passed. Each file name will be postfixed
704 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
707 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
709 Creates a date input field, with an attached javascript that will open a
710 calendar on click. The javascript ist by default anchoered at the bottom right
711 sight. This can be overridden with C<cal_align>, see Calendar documentation for
712 the details, usually you'll want a two letter abbreviation of the alignment.
713 Right + Bottom becomes C<BL>.
715 =item C<tabbed \@tab, %attributes>
717 Will create a tabbed area. The tabs should be created with the helper function
721 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
722 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
725 An optional attribute is C<selected>, which accepts the ordinal of a tab which
726 should be selected by default.
728 =item C<areainput_tag $name, $content, %PARAMS>
730 Creates a generic input tag or textarea tag, depending on content size. The
731 amount of desired rows must be either given with the C<rows> parameter or can
732 be computed from the value and the C<cols> paramter, Accepted parameters
733 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
735 You can force input by setting rows to 1, and you can force textarea by setting
738 =item C<multiselect2side $id, %params>
740 Creates a JavaScript snippet calling the jQuery function
741 C<multiselect2side> on the select control with the ID C<$id>. The
742 select itself is not created. C<%params> can contain the following
749 The label of the list of available options. Defaults to the
750 translation of 'Available'.
754 The label of the list of selected options. Defaults to the
755 translation of 'Selected'.
759 =item C<sortable_element $selector, %params>
761 Makes the children of the DOM element C<$selector> (a jQuery selector)
762 sortable with the I<jQuery UI Selectable> library. The children can be
763 dragged & dropped around. After dropping an element an URL can be
764 postet to with the element IDs of the sorted children.
766 If this is used then the JavaScript file C<js/jquery-ui.js> must be
767 included manually as well as it isn't loaded via C<$::form-gt;header>.
769 C<%params> can contain the following entries:
775 The URL to POST an AJAX request to after a dragged element has been
776 dropped. The AJAX request's return value is ignored. If given then
777 C<$params{with}> must be given as well.
781 A string that is interpreted as the prefix of the children's ID. Upon
782 POSTing the result each child whose ID starts with C<$params{with}> is
783 considered. The prefix and the following "_" is removed from the
784 ID. The remaining parts of the IDs of those children are posted as a
785 single array parameter. The array parameter's name is either
786 C<$params{as}> or, missing that, C<$params{with}>.
790 Sets the POST parameter name for AJAX request after dropping an
791 element (see C<$params{with}>).
795 An optional jQuery selector specifying which part of the child element
796 is dragable. If the parameter is not given then it defaults to
797 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
798 parameter is set and empty then the whole child element is dragable,
799 and clicks through to underlying elements like inputs or links might
802 =item C<dont_recolor>
804 If trueish then the children will not be recolored. The default is to
805 recolor the children by setting the class C<listrow0> on odd and
806 C<listrow1> on even entries.
812 <script type="text/javascript" src="js/jquery-ui.js"></script>
814 <table id="thing_list">
816 <tr><td>This</td><td>That</td></tr>
819 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
820 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
821 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
825 [% L.sortable_element('#thing_list tbody',
826 url => 'controller.pl?action=SystemThings/reorder',
829 recolor_rows => 1) %]
831 After dropping e.g. the third element at the top of the list a POST
832 request would be made to the C<reorder> action of the C<SystemThings>
833 controller with a single parameter called C<thing_ids> -- an array
834 containing the values C<[ 6, 2, 15 ]>.
838 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
842 =head2 CONVERSION FUNCTIONS
846 =item C<options_for_select \@collection, %options>
848 Creates a string suitable for a HTML 'select' tag consisting of one
849 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
850 to use and the title to display are extracted from the elements in
851 C<\@collection>. Each element can be one of four things:
855 =item 1. An array reference with at least two elements. The first element is
856 the value, the second element is its title.
858 =item 2. A scalar. The scalar is both the value and the title.
860 =item 3. A hash reference. In this case C<%options> must contain
861 I<value> and I<title> keys that name the keys in the element to use
862 for the value and title respectively.
864 =item 4. A blessed reference. In this case C<%options> must contain
865 I<value> and I<title> keys that name functions called on the blessed
866 reference whose return values are used as the value and title
871 For cases 3 and 4 C<$options{value}> defaults to C<id> and
872 C<$options{title}> defaults to C<$options{value}>.
874 In addition to pure keys/method you can also provide coderefs as I<value_sub>
875 and/or I<title_sub>. If present, these take precedence over keys or methods,
876 and are called with the element as first argument. It must return the value or
879 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
880 precedence over each individual sub. It will only be called once for each
881 element and must return a list of value and title.
883 If the option C<with_empty> is set then an empty element (value
884 C<undef>) will be used as the first element. The title to display for
885 this element can be set with the option C<empty_title> and defaults to
888 The option C<default> can be either a scalar or an array reference
889 containing the values of the options which should be set to be
892 =item C<tab, description, target, %PARAMS>
894 Creates a tab for C<tabbed>. The description will be used as displayed name.
895 The target should be a block or template that can be processed. C<tab> supports
896 a C<method> parameter, which can override the process method to apply target.
897 C<method => 'raw'> will just include the given text as is. I was too lazy to
898 implement C<include> properly.
900 Also an C<if> attribute is supported, so that tabs can be suppressed based on
901 some occasion. In this case the supplied block won't even get processed, and
902 the resulting tab will get ignored by C<tabbed>:
904 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
908 =head1 MODULE AUTHORS
910 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
912 L<http://linet-services.de>