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, $name, $value) = splice @_, 0, 3;
 
 257   my %attributes            = _hashify(@_);
 
 259   my $options               = $self->options_for_select([ [ 1, $::locale->text('Yes') ], [ 0, $::locale->text('No') ] ], default => $value ? 1 : 0);
 
 260   return $self->select_tag($name, $options, %attributes);
 
 264   my ($self, $data) = @_;
 
 265   return $self->html_tag('script', $data, type => 'text/javascript');
 
 272   foreach my $file (@_) {
 
 273     $file .= '.css'        unless $file =~ m/\.css$/;
 
 274     $file  = "css/${file}" unless $file =~ m|/|;
 
 276     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
 
 283   my ($self, $name, $value, @slurp) = @_;
 
 284   my %params   = _hashify(@slurp);
 
 285   my $name_e   = _H($name);
 
 287   my $datefmt  = apply {
 
 291   } $::myconfig{"dateformat"};
 
 293   my $cal_align = delete $params{cal_align} || 'BR';
 
 294   my $onchange  = delete $params{onchange};
 
 295   my $str_value = blessed $value ? $value->to_lxoffice : $value;
 
 297   $self->input_tag($name, $str_value,
 
 300     title  => _H($::myconfig{dateformat}),
 
 301     onBlur => 'check_right_date_format(this)',
 
 303     onChange => $onchange,
 
 306   ) . ((!$params{no_cal} && !$params{readonly}) ?
 
 307   $self->html_tag('img', undef,
 
 308     src    => 'image/calendar.png',
 
 309     alt    => $::locale->text('Calendar'),
 
 311     title  => _H($::myconfig{dateformat}),
 
 315     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
 
 319 sub customer_picker {
 
 320   my ($self, $name, $value, %params) = @_;
 
 321   my $name_e    = _H($name);
 
 323   $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
 
 324   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
 
 325   $self->javascript(<<JS);
 
 326 function autocomplete_customer (selector, column) {
 
 327   \$(function(){ \$(selector).autocomplete({
 
 328     source: function(req, rsp) {
 
 330         url: 'controller.pl?action=Customer/ajax_autocomplete',
 
 335           current: function() { \$('#$name_e').val() },
 
 338         success: function (data){ rsp(data) }
 
 343     select: function(event, ui) {
 
 344       \$('#$name_e').val(ui.item.id);
 
 345       \$('#$name_e\_name').val(ui.item.name);
 
 349 autocomplete_customer('#$name_e\_name');
 
 353 # simple version with select_tag
 
 354 sub vendor_selector {
 
 355   my ($self, $name, $value, %params) = @_;
 
 357   my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
 
 358                          (ref $value && $value->can('id')) ? $value->id : '';
 
 359   my $options_str = $self->options_for_select(SL::DB::Manager::Vendor->get_all(),
 
 360                                               default      => $actual_vendor_id,
 
 361                                               title_sub    => sub { $_[0]->vendornumber . " : " . $_[0]->name },
 
 364   return $self->select_tag($name, $options_str, %params);
 
 368 # simple version with select_tag
 
 370   my ($self, $name, $value, %params) = @_;
 
 372   my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
 
 373                        (ref $value && $value->can('id')) ? $value->id : '';
 
 374   my $options_str = $self->options_for_select(SL::DB::Manager::Part->get_all(),
 
 375                                               default      => $actual_part_id,
 
 376                                               title_sub    => sub { $_[0]->partnumber . " : " . $_[0]->description },
 
 379   return $self->select_tag($name, $options_str, %params);
 
 387   foreach my $file (@_) {
 
 388     $file .= '.js'        unless $file =~ m/\.js$/;
 
 389     $file  = "js/${file}" unless $file =~ m|/|;
 
 391     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
 
 398   my ($self, $tabs, @slurp) = @_;
 
 399   my %params   = _hashify(@slurp);
 
 400   my $id       = $params{id} || 'tab_' . _tag_id();
 
 402   $params{selected} *= 1;
 
 404   die 'L.tabbed needs an arrayred of tabs for first argument'
 
 405     unless ref $tabs eq 'ARRAY';
 
 407   my (@header, @blocks);
 
 408   for my $i (0..$#$tabs) {
 
 409     my $tab = $tabs->[$i];
 
 413     my $selected = $params{selected} == $i;
 
 414     my $tab_id   = "__tab_id_$i";
 
 415     push @header, $self->li_tag(
 
 416       $self->link('', $tab->{name}, rel => $tab_id),
 
 417         ($selected ? (class => 'selected') : ())
 
 419     push @blocks, $self->div_tag($tab->{data},
 
 420       id => $tab_id, class => 'tabcontent');
 
 423   return '' unless @header;
 
 424   return $self->ul_tag(
 
 425     join('', @header), id => $id, class => 'shadetabs'
 
 428     join('', @blocks), class => 'tabcontentstyle'
 
 431     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
 
 432     qq|$id.setselectedClassTarget("link");$id.init();|
 
 437   my ($self, $name, $src, @slurp) = @_;
 
 438   my %params = _hashify(@slurp);
 
 440   $params{method} ||= 'process';
 
 442   return () if defined $params{if} && !$params{if};
 
 445   if ($params{method} eq 'raw') {
 
 447   } elsif ($params{method} eq 'process') {
 
 448     $data = $self->_context->process($src, %{ $params{args} || {} });
 
 450     die "unknown tag method '$params{method}'";
 
 453   return () unless $data;
 
 455   return +{ name => $name, data => $data };
 
 459   my ($self, $name, $value, @slurp) = @_;
 
 460   my %attributes      = _hashify(@slurp);
 
 463   my $min  = delete $attributes{min_rows} || 1;
 
 465   if (exists $attributes{cols}) {
 
 466     $cols = delete $attributes{cols};
 
 467     $rows = $::form->numtextrows($value, $cols);
 
 469     $rows = delete $attributes{rows} || 1;
 
 473     ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
 
 474     : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
 
 477 sub multiselect2side {
 
 478   my ($self, $id, @slurp) = @_;
 
 479   my %params              = _hashify(@slurp);
 
 481   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
 
 482   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
 
 483   $params{moveOptions}    = 'false';
 
 485   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
 
 487 <script type="text/javascript">
 
 488   \$().ready(function() {
 
 489     \$('#${id}').multiselect2side({ ${vars} });
 
 497 sub sortable_element {
 
 498   my ($self, $selector, @slurp) = @_;
 
 499   my %params                    = _hashify(@slurp);
 
 501   my %attributes = ( distance => 5,
 
 502                      helper   => <<'JAVASCRIPT' );
 
 503     function(event, ui) {
 
 504       ui.children().each(function() {
 
 505         $(this).width($(this).width());
 
 513   if ($params{url} && $params{with}) {
 
 514     my $as      = $params{as} || $params{with};
 
 515     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
 
 516     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
 
 518     $stop_event = <<JAVASCRIPT;
 
 519         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
 
 523   if (!$params{dont_recolor}) {
 
 524     $stop_event .= <<JAVASCRIPT;
 
 525         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
 
 526         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
 
 531     $attributes{stop} = <<JAVASCRIPT;
 
 532       function(event, ui) {
 
 539   $params{handle}     = '.dragdrop' unless exists $params{handle};
 
 540   $attributes{handle} = "'$params{handle}'" if $params{handle};
 
 542   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
 
 544   my $code = <<JAVASCRIPT;
 
 545 <script type="text/javascript">
 
 547     \$( "${selector}" ).sortable({ ${attr_str} })
 
 555 sub online_help_tag {
 
 556   my ($self, $tag, @slurp) = @_;
 
 557   my %params               = _hashify(@slurp);
 
 558   my $cc                   = $::myconfig{countrycode};
 
 559   my $file                 = "doc/online/$cc/$tag.html";
 
 560   my $text                 = $params{text} || $::locale->text('Help');
 
 562   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
 
 563   return unless -f $file;
 
 564   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
 
 569   require Data::Dumper;
 
 570   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
 
 574   my ($self, $text, @slurp) = @_;
 
 575   my %params                = _hashify(@slurp);
 
 578   $params{at}               =  3 if 3 > $params{at};
 
 581   return $text if length($text) < $params{at};
 
 582   return substr($text, 0, $params{at}) . '...';
 
 591 SL::Templates::Plugin::L -- Layouting / tag generation
 
 595 Usage from a template:
 
 599   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
 
 601   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
 
 602                                                       { direction => 'right', display => 'To the right' } ],
 
 603                                                     value => 'direction', title => 'display', default => 'right')) %]
 
 607 A module modeled a bit after Rails' ActionView helpers. Several small
 
 608 functions that create HTML tags from various kinds of data sources.
 
 612 =head2 LOW-LEVEL FUNCTIONS
 
 616 =item C<name_to_id $name>
 
 618 Converts a name to a HTML id by replacing various characters.
 
 620 =item C<attributes %items>
 
 622 Creates a string from all elements in C<%items> suitable for usage as
 
 623 HTML tag attributes. Keys and values are HTML escaped even though keys
 
 624 must not contain non-ASCII characters for browsers to accept them.
 
 626 =item C<html_tag $tag_name, $content_string, %attributes>
 
 628 Creates an opening and closing HTML tag for C<$tag_name> and puts
 
 629 C<$content_string> between the two. If C<$content_string> is undefined
 
 630 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
 
 631 are key/value pairs added to the opening tag.
 
 633 C<$content_string> is not HTML escaped.
 
 637 =head2 HIGH-LEVEL FUNCTIONS
 
 641 =item C<select_tag $name, $options_string, %attributes>
 
 643 Creates a HTML 'select' tag named C<$name> with the contents
 
 644 C<$options_string> and with arbitrary HTML attributes from
 
 645 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
 
 647 The C<$options_string> is usually created by the
 
 648 L</options_for_select> function. If C<$options_string> is an array
 
 649 reference then it will be passed to L</options_for_select>
 
 652 =item C<yes_no_tag $name, $value, %attributes>
 
 654 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
 
 655 calling L<select_tag> and L<options_for_select>. C<$value> determines
 
 656 which entry is selected. The C<%attributes> are passed through to
 
 659 =item C<input_tag $name, $value, %attributes>
 
 661 Creates a HTML 'input type=text' tag named C<$name> with the value
 
 662 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 663 tag's C<id> defaults to C<name_to_id($name)>.
 
 665 =item C<hidden_tag $name, $value, %attributes>
 
 667 Creates a HTML 'input type=hidden' tag named C<$name> with the value
 
 668 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 669 tag's C<id> defaults to C<name_to_id($name)>.
 
 671 =item C<submit_tag $name, $value, %attributes>
 
 673 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
 
 674 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 675 tag's C<id> defaults to C<name_to_id($name)>.
 
 677 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
 
 678 be added via the C<onclick> handler asking the question given with
 
 679 C<$attributes{confirm}>. If request is only submitted if the user
 
 680 clicks the dialog's ok/yes button.
 
 682 =item C<textarea_tag $name, $value, %attributes>
 
 684 Creates a HTML 'textarea' tag named C<$name> with the content
 
 685 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 686 tag's C<id> defaults to C<name_to_id($name)>.
 
 688 =item C<checkbox_tag $name, %attributes>
 
 690 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
 
 691 HTML attributes from C<%attributes>. The tag's C<id> defaults to
 
 692 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
 
 694 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 695 created with said C<label>. No attribute named C<label> is created in
 
 698 If C<%attributes> contains a key C<checkall> then the value is taken as a
 
 699 JQuery selector and clicking this checkbox will also toggle all checkboxes
 
 700 matching the selector.
 
 702 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 704 Creates a date input field, with an attached javascript that will open a
 
 705 calendar on click. The javascript ist by default anchoered at the bottom right
 
 706 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 707 the details, usually you'll want a two letter abbreviation of the alignment.
 
 708 Right + Bottom becomes C<BL>.
 
 710 =item C<radio_button_tag $name, %attributes>
 
 712 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
 
 713 HTML attributes from C<%attributes>. The tag's C<value> defaults to
 
 714 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
 
 716 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
 
 717 created with said C<label>. No attribute named C<label> is created in
 
 720 =item C<javascript_tag $file1, $file2, $file3...>
 
 722 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
 
 723 tag for each file name parameter passed. Each file name will be
 
 724 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
 
 725 doesn't contain a slash.
 
 727 =item C<stylesheet_tag $file1, $file2, $file3...>
 
 729 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
 
 730 for each file name parameter passed. Each file name will be postfixed
 
 731 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
 
 734 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
 
 736 Creates a date input field, with an attached javascript that will open a
 
 737 calendar on click. The javascript ist by default anchoered at the bottom right
 
 738 sight. This can be overridden with C<cal_align>, see Calendar documentation for
 
 739 the details, usually you'll want a two letter abbreviation of the alignment.
 
 740 Right + Bottom becomes C<BL>.
 
 742 =item C<tabbed \@tab, %attributes>
 
 744 Will create a tabbed area. The tabs should be created with the helper function
 
 748     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
 
 749     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
 
 752 An optional attribute is C<selected>, which accepts the ordinal of a tab which
 
 753 should be selected by default.
 
 755 =item C<areainput_tag $name, $content, %PARAMS>
 
 757 Creates a generic input tag or textarea tag, depending on content size. The
 
 758 amount of desired rows must be either given with the C<rows> parameter or can
 
 759 be computed from the value and the C<cols> paramter, Accepted parameters
 
 760 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
 
 762 You can force input by setting rows to 1, and you can force textarea by setting
 
 765 =item C<multiselect2side $id, %params>
 
 767 Creates a JavaScript snippet calling the jQuery function
 
 768 C<multiselect2side> on the select control with the ID C<$id>. The
 
 769 select itself is not created. C<%params> can contain the following
 
 776 The label of the list of available options. Defaults to the
 
 777 translation of 'Available'.
 
 781 The label of the list of selected options. Defaults to the
 
 782 translation of 'Selected'.
 
 786 =item C<sortable_element $selector, %params>
 
 788 Makes the children of the DOM element C<$selector> (a jQuery selector)
 
 789 sortable with the I<jQuery UI Selectable> library. The children can be
 
 790 dragged & dropped around. After dropping an element an URL can be
 
 791 postet to with the element IDs of the sorted children.
 
 793 If this is used then the JavaScript file C<js/jquery-ui.js> must be
 
 794 included manually as well as it isn't loaded via C<$::form-gt;header>.
 
 796 C<%params> can contain the following entries:
 
 802 The URL to POST an AJAX request to after a dragged element has been
 
 803 dropped. The AJAX request's return value is ignored. If given then
 
 804 C<$params{with}> must be given as well.
 
 808 A string that is interpreted as the prefix of the children's ID. Upon
 
 809 POSTing the result each child whose ID starts with C<$params{with}> is
 
 810 considered. The prefix and the following "_" is removed from the
 
 811 ID. The remaining parts of the IDs of those children are posted as a
 
 812 single array parameter. The array parameter's name is either
 
 813 C<$params{as}> or, missing that, C<$params{with}>.
 
 817 Sets the POST parameter name for AJAX request after dropping an
 
 818 element (see C<$params{with}>).
 
 822 An optional jQuery selector specifying which part of the child element
 
 823 is dragable. If the parameter is not given then it defaults to
 
 824 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
 
 825 parameter is set and empty then the whole child element is dragable,
 
 826 and clicks through to underlying elements like inputs or links might
 
 829 =item C<dont_recolor>
 
 831 If trueish then the children will not be recolored. The default is to
 
 832 recolor the children by setting the class C<listrow0> on odd and
 
 833 C<listrow1> on even entries.
 
 839   <script type="text/javascript" src="js/jquery-ui.js"></script>
 
 841   <table id="thing_list">
 
 843       <tr><td>This</td><td>That</td></tr>
 
 846       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
 
 847       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
 
 848       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
 
 852   [% L.sortable_element('#thing_list tbody',
 
 853                         url          => 'controller.pl?action=SystemThings/reorder',
 
 856                         recolor_rows => 1) %]
 
 858 After dropping e.g. the third element at the top of the list a POST
 
 859 request would be made to the C<reorder> action of the C<SystemThings>
 
 860 controller with a single parameter called C<thing_ids> -- an array
 
 861 containing the values C<[ 6, 2, 15 ]>.
 
 865 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
 
 869 =head2 CONVERSION FUNCTIONS
 
 873 =item C<options_for_select \@collection, %options>
 
 875 Creates a string suitable for a HTML 'select' tag consisting of one
 
 876 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
 
 877 to use and the title to display are extracted from the elements in
 
 878 C<\@collection>. Each element can be one of four things:
 
 882 =item 1. An array reference with at least two elements. The first element is
 
 883 the value, the second element is its title.
 
 885 =item 2. A scalar. The scalar is both the value and the title.
 
 887 =item 3. A hash reference. In this case C<%options> must contain
 
 888 I<value> and I<title> keys that name the keys in the element to use
 
 889 for the value and title respectively.
 
 891 =item 4. A blessed reference. In this case C<%options> must contain
 
 892 I<value> and I<title> keys that name functions called on the blessed
 
 893 reference whose return values are used as the value and title
 
 898 For cases 3 and 4 C<$options{value}> defaults to C<id> and
 
 899 C<$options{title}> defaults to C<$options{value}>.
 
 901 In addition to pure keys/method you can also provide coderefs as I<value_sub>
 
 902 and/or I<title_sub>. If present, these take precedence over keys or methods,
 
 903 and are called with the element as first argument. It must return the value or
 
 906 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
 
 907 precedence over each individual sub. It will only be called once for each
 
 908 element and must return a list of value and title.
 
 910 If the option C<with_empty> is set then an empty element (value
 
 911 C<undef>) will be used as the first element. The title to display for
 
 912 this element can be set with the option C<empty_title> and defaults to
 
 915 The option C<default> can be either a scalar or an array reference
 
 916 containing the values of the options which should be set to be
 
 919 =item C<tab, description, target, %PARAMS>
 
 921 Creates a tab for C<tabbed>. The description will be used as displayed name.
 
 922 The target should be a block or template that can be processed. C<tab> supports
 
 923 a C<method> parameter, which can override the process method to apply target.
 
 924 C<method => 'raw'> will just include the given text as is. I was too lazy to
 
 925 implement C<include> properly.
 
 927 Also an C<if> attribute is supported, so that tabs can be suppressed based on
 
 928 some occasion. In this case the supplied block won't even get processed, and
 
 929 the resulting tab will get ignored by C<tabbed>:
 
 931   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
 
 933 =item C<truncate $text, %params>
 
 935 Returns the C<$text> truncated after a certain number of
 
 938 The number of characters to truncate at is determined by the parameter
 
 939 C<at> which defaults to 50. If the text is longer than C<$params{at}>
 
 940 then it will be truncated and postfixed with '...'. Otherwise it will
 
 941 be returned unmodified.
 
 945 =head1 MODULE AUTHORS
 
 947 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 949 L<http://linet-services.de>