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');
 
 349   foreach my $file (@_) {
 
 350     $file .= '.js'        unless $file =~ m/\.js$/;
 
 351     $file  = "js/${file}" unless $file =~ m|/|;
 
 353     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
 
 360   my ($self, $tabs, @slurp) = @_;
 
 361   my %params   = _hashify(@slurp);
 
 362   my $id       = $params{id} || 'tab_' . _tag_id();
 
 364   $params{selected} *= 1;
 
 366   die 'L.tabbed needs an arrayred of tabs for first argument'
 
 367     unless ref $tabs eq 'ARRAY';
 
 369   my (@header, @blocks);
 
 370   for my $i (0..$#$tabs) {
 
 371     my $tab = $tabs->[$i];
 
 375     my $selected = $params{selected} == $i;
 
 376     my $tab_id   = "__tab_id_$i";
 
 377     push @header, $self->li_tag(
 
 378       $self->link('', $tab->{name}, rel => $tab_id),
 
 379         ($selected ? (class => 'selected') : ())
 
 381     push @blocks, $self->div_tag($tab->{data},
 
 382       id => $tab_id, class => 'tabcontent');
 
 385   return '' unless @header;
 
 386   return $self->ul_tag(
 
 387     join('', @header), id => $id, class => 'shadetabs'
 
 390     join('', @blocks), class => 'tabcontentstyle'
 
 393     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
 
 394     qq|$id.setselectedClassTarget("link");$id.init();|
 
 399   my ($self, $name, $src, @slurp) = @_;
 
 400   my %params = _hashify(@slurp);
 
 402   $params{method} ||= 'process';
 
 404   return () if defined $params{if} && !$params{if};
 
 407   if ($params{method} eq 'raw') {
 
 409   } elsif ($params{method} eq 'process') {
 
 410     $data = $self->_context->process($src, %{ $params{args} || {} });
 
 412     die "unknown tag method '$params{method}'";
 
 415   return () unless $data;
 
 417   return +{ name => $name, data => $data };
 
 421   my ($self, $name, $value, @slurp) = @_;
 
 422   my %attributes      = _hashify(@slurp);
 
 425   my $min  = delete $attributes{min_rows} || 1;
 
 427   if (exists $attributes{cols}) {
 
 428     $cols = delete $attributes{cols};
 
 429     $rows = $::form->numtextrows($value, $cols);
 
 431     $rows = delete $attributes{rows} || 1;
 
 435     ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
 
 436     : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
 
 439 sub multiselect2side {
 
 440   my ($self, $id, @slurp) = @_;
 
 441   my %params              = _hashify(@slurp);
 
 443   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
 
 444   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
 
 445   $params{moveOptions}    = 'false';
 
 447   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
 
 449 <script type="text/javascript">
 
 450   \$().ready(function() {
 
 451     \$('#${id}').multiselect2side({ ${vars} });
 
 459 sub sortable_element {
 
 460   my ($self, $selector, @slurp) = @_;
 
 461   my %params                    = _hashify(@slurp);
 
 463   my %attributes = ( distance => 5,
 
 464                      helper   => <<'JAVASCRIPT' );
 
 465     function(event, ui) {
 
 466       ui.children().each(function() {
 
 467         $(this).width($(this).width());
 
 475   if ($params{url} && $params{with}) {
 
 476     my $as      = $params{as} || $params{with};
 
 477     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
 
 478     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
 
 480     $stop_event = <<JAVASCRIPT;
 
 481         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
 
 485   if (!$params{dont_recolor}) {
 
 486     $stop_event .= <<JAVASCRIPT;
 
 487         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
 
 488         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
 
 493     $attributes{stop} = <<JAVASCRIPT;
 
 494       function(event, ui) {
 
 501   $params{handle}     = '.dragdrop' unless exists $params{handle};
 
 502   $attributes{handle} = "'$params{handle}'" if $params{handle};
 
 504   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
 
 506   my $code = <<JAVASCRIPT;
 
 507 <script type="text/javascript">
 
 509     \$( "${selector}" ).sortable({ ${attr_str} })
 
 517 sub online_help_tag {
 
 518   my ($self, $tag, @slurp) = @_;
 
 519   my %params               = _hashify(@slurp);
 
 520   my $cc                   = $::myconfig{countrycode};
 
 521   my $file                 = "doc/online/$cc/$tag.html";
 
 522   my $text                 = $params{text} || $::locale->text('Help');
 
 524   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
 
 525   return unless -f $file;
 
 526   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
 
 531   require Data::Dumper;
 
 532   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
 
 541 SL::Templates::Plugin::L -- Layouting / tag generation
 
 545 Usage from a template:
 
 549   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
 
 551   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
 
 552                                                       { direction => 'right', display => 'To the right' } ],
 
 553                                                     value => 'direction', title => 'display', default => 'right')) %]
 
 557 A module modeled a bit after Rails' ActionView helpers. Several small
 
 558 functions that create HTML tags from various kinds of data sources.
 
 562 =head2 LOW-LEVEL FUNCTIONS
 
 566 =item C<name_to_id $name>
 
 568 Converts a name to a HTML id by replacing various characters.
 
 570 =item C<attributes %items>
 
 572 Creates a string from all elements in C<%items> suitable for usage as
 
 573 HTML tag attributes. Keys and values are HTML escaped even though keys
 
 574 must not contain non-ASCII characters for browsers to accept them.
 
 576 =item C<html_tag $tag_name, $content_string, %attributes>
 
 578 Creates an opening and closing HTML tag for C<$tag_name> and puts
 
 579 C<$content_string> between the two. If C<$content_string> is undefined
 
 580 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
 
 581 are key/value pairs added to the opening tag.
 
 583 C<$content_string> is not HTML escaped.
 
 587 =head2 HIGH-LEVEL FUNCTIONS
 
 591 =item C<select_tag $name, $options_string, %attributes>
 
 593 Creates a HTML 'select' tag named C<$name> with the contents
 
 594 C<$options_string> and with arbitrary HTML attributes from
 
 595 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
 
 597 The C<$options_string> is usually created by the
 
 598 L</options_for_select> function. If C<$options_string> is an array
 
 599 reference then it will be passed to L</options_for_select>
 
 602 =item C<input_tag $name, $value, %attributes>
 
 604 Creates a HTML 'input type=text' tag named C<$name> with the value
 
 605 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 606 tag's C<id> defaults to C<name_to_id($name)>.
 
 608 =item C<hidden_tag $name, $value, %attributes>
 
 610 Creates a HTML 'input type=hidden' tag named C<$name> with the value
 
 611 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 612 tag's C<id> defaults to C<name_to_id($name)>.
 
 614 =item C<submit_tag $name, $value, %attributes>
 
 616 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
 
 617 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 618 tag's C<id> defaults to C<name_to_id($name)>.
 
 620 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
 
 621 be added via the C<onclick> handler asking the question given with
 
 622 C<$attributes{confirm}>. If request is only submitted if the user
 
 623 clicks the dialog's ok/yes button.
 
 625 =item C<textarea_tag $name, $value, %attributes>
 
 627 Creates a HTML 'textarea' tag named C<$name> with the content
 
 628 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 629 tag's C<id> defaults to C<name_to_id($name)>.
 
 631 =item C<checkbox_tag $name, %attributes>
 
 633 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
 
 634 HTML attributes from C<%attributes>. The tag's C<id> defaults to
 
 635 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
 
 637 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 638 created with said C<label>. No attribute named C<label> is created in
 
 641 If C<%attributes> contains a key C<checkall> then the value is taken as a
 
 642 JQuery selector and clicking this checkbox will also toggle all checkboxes
 
 643 matching the selector.
 
 645 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 647 Creates a date input field, with an attached javascript that will open a
 
 648 calendar on click. The javascript ist by default anchoered at the bottom right
 
 649 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 650 the details, usually you'll want a two letter abbreviation of the alignment.
 
 651 Right + Bottom becomes C<BL>.
 
 653 =item C<radio_button_tag $name, %attributes>
 
 655 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
 
 656 HTML attributes from C<%attributes>. The tag's C<value> defaults to
 
 657 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
 
 659 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 660 created with said C<label>. No attribute named C<label> is created in
 
 663 =item C<javascript_tag $file1, $file2, $file3...>
 
 665 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
 
 666 tag for each file name parameter passed. Each file name will be
 
 667 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
 
 668 doesn't contain a slash.
 
 670 =item C<stylesheet_tag $file1, $file2, $file3...>
 
 672 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
 
 673 for each file name parameter passed. Each file name will be postfixed
 
 674 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
 
 677 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 679 Creates a date input field, with an attached javascript that will open a
 
 680 calendar on click. The javascript ist by default anchoered at the bottom right
 
 681 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 682 the details, usually you'll want a two letter abbreviation of the alignment.
 
 683 Right + Bottom becomes C<BL>.
 
 685 =item C<tabbed \@tab, %attributes>
 
 687 Will create a tabbed area. The tabs should be created with the helper function
 
 691     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
 
 692     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
 
 695 An optional attribute is C<selected>, which accepts the ordinal of a tab which
 
 696 should be selected by default.
 
 698 =item C<areainput_tag $name, $content, %PARAMS>
 
 700 Creates a generic input tag or textarea tag, depending on content size. The
 
 701 amount of desired rows must be either given with the C<rows> parameter or can
 
 702 be computed from the value and the C<cols> paramter, Accepted parameters
 
 703 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
 
 705 You can force input by setting rows to 1, and you can force textarea by setting
 
 708 =item C<multiselect2side $id, %params>
 
 710 Creates a JavaScript snippet calling the jQuery function
 
 711 C<multiselect2side> on the select control with the ID C<$id>. The
 
 712 select itself is not created. C<%params> can contain the following
 
 719 The label of the list of available options. Defaults to the
 
 720 translation of 'Available'.
 
 724 The label of the list of selected options. Defaults to the
 
 725 translation of 'Selected'.
 
 729 =item C<sortable_element $selector, %params>
 
 731 Makes the children of the DOM element C<$selector> (a jQuery selector)
 
 732 sortable with the I<jQuery UI Selectable> library. The children can be
 
 733 dragged & dropped around. After dropping an element an URL can be
 
 734 postet to with the element IDs of the sorted children.
 
 736 If this is used then the JavaScript file C<js/jquery-ui.js> must be
 
 737 included manually as well as it isn't loaded via C<$::form-gt;header>.
 
 739 C<%params> can contain the following entries:
 
 745 The URL to POST an AJAX request to after a dragged element has been
 
 746 dropped. The AJAX request's return value is ignored. If given then
 
 747 C<$params{with}> must be given as well.
 
 751 A string that is interpreted as the prefix of the children's ID. Upon
 
 752 POSTing the result each child whose ID starts with C<$params{with}> is
 
 753 considered. The prefix and the following "_" is removed from the
 
 754 ID. The remaining parts of the IDs of those children are posted as a
 
 755 single array parameter. The array parameter's name is either
 
 756 C<$params{as}> or, missing that, C<$params{with}>.
 
 760 Sets the POST parameter name for AJAX request after dropping an
 
 761 element (see C<$params{with}>).
 
 765 An optional jQuery selector specifying which part of the child element
 
 766 is dragable. If the parameter is not given then it defaults to
 
 767 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
 
 768 parameter is set and empty then the whole child element is dragable,
 
 769 and clicks through to underlying elements like inputs or links might
 
 772 =item C<dont_recolor>
 
 774 If trueish then the children will not be recolored. The default is to
 
 775 recolor the children by setting the class C<listrow0> on odd and
 
 776 C<listrow1> on even entries.
 
 782   <script type="text/javascript" src="js/jquery-ui.js"></script>
 
 784   <table id="thing_list">
 
 786       <tr><td>This</td><td>That</td></tr>
 
 789       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
 
 790       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
 
 791       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
 
 795   [% L.sortable_element('#thing_list tbody',
 
 796                         url          => 'controller.pl?action=SystemThings/reorder',
 
 799                         recolor_rows => 1) %]
 
 801 After dropping e.g. the third element at the top of the list a POST
 
 802 request would be made to the C<reorder> action of the C<SystemThings>
 
 803 controller with a single parameter called C<thing_ids> -- an array
 
 804 containing the values C<[ 6, 2, 15 ]>.
 
 808 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
 
 812 =head2 CONVERSION FUNCTIONS
 
 816 =item C<options_for_select \@collection, %options>
 
 818 Creates a string suitable for a HTML 'select' tag consisting of one
 
 819 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
 
 820 to use and the title to display are extracted from the elements in
 
 821 C<\@collection>. Each element can be one of four things:
 
 825 =item 1. An array reference with at least two elements. The first element is
 
 826 the value, the second element is its title.
 
 828 =item 2. A scalar. The scalar is both the value and the title.
 
 830 =item 3. A hash reference. In this case C<%options> must contain
 
 831 I<value> and I<title> keys that name the keys in the element to use
 
 832 for the value and title respectively.
 
 834 =item 4. A blessed reference. In this case C<%options> must contain
 
 835 I<value> and I<title> keys that name functions called on the blessed
 
 836 reference whose return values are used as the value and title
 
 841 For cases 3 and 4 C<$options{value}> defaults to C<id> and
 
 842 C<$options{title}> defaults to C<$options{value}>.
 
 844 In addition to pure keys/method you can also provide coderefs as I<value_sub>
 
 845 and/or I<title_sub>. If present, these take precedence over keys or methods,
 
 846 and are called with the element as first argument. It must return the value or
 
 849 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
 
 850 precedence over each individual sub. It will only be called once for each
 
 851 element and must return a list of value and title.
 
 853 If the option C<with_empty> is set then an empty element (value
 
 854 C<undef>) will be used as the first element. The title to display for
 
 855 this element can be set with the option C<empty_title> and defaults to
 
 858 The option C<default> can be either a scalar or an array reference
 
 859 containing the values of the options which should be set to be
 
 862 =item C<tab, description, target, %PARAMS>
 
 864 Creates a tab for C<tabbed>. The description will be used as displayed name.
 
 865 The target should be a block or template that can be processed. C<tab> supports
 
 866 a C<method> parameter, which can override the process method to apply target.
 
 867 C<method => 'raw'> will just include the given text as is. I was too lazy to
 
 868 implement C<include> properly.
 
 870 Also an C<if> attribute is supported, so that tabs can be suppressed based on
 
 871 some occasion. In this case the supplied block won't even get processed, and
 
 872 the resulting tab will get ignored by C<tabbed>:
 
 874   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
 
 878 =head1 MODULE AUTHORS
 
 880 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 882 L<http://linet-services.de>