Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
[kivitendo-erp.git] / SL / Template / Plugin / L.pm
1 package SL::Template::Plugin::L;
2
3 use base qw( Template::Plugin );
4 use Template::Plugin;
5 use List::MoreUtils qw(apply);
6 use List::Util qw(max);
7 use Scalar::Util qw(blessed);
8
9 use strict;
10
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;
15 sub _tag_id {
16   return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
17 }
18 }
19
20 my %_valueless_attributes = map { $_ => 1 } qw(
21   checked compact declare defer disabled ismap multiple noresize noshade nowrap
22   readonly selected
23 );
24
25 sub _H {
26   my $string = shift;
27   return $::locale->quote_special_chars('HTML', $string);
28 }
29
30 sub _J {
31   my $string =  "" . shift;
32   $string    =~ s/\"/\\\"/g;
33   return $string;
34 }
35
36 sub _hashify {
37   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
38 }
39
40 sub new {
41   my ($class, $context, @args) = @_;
42
43   return bless {
44     CONTEXT => $context,
45   }, $class;
46 }
47
48 sub _context {
49   die 'not an accessor' if @_ > 1;
50   return $_[0]->{CONTEXT};
51 }
52
53 sub name_to_id {
54   my $self =  shift;
55   my $name =  shift;
56
57   $name    =~ s/[^\w_]/_/g;
58   $name    =~ s/_+/_/g;
59
60   return $name;
61 }
62
63 sub attributes {
64   my ($self, @slurp)    = @_;
65   my %options = _hashify(@slurp);
66
67   my @result = ();
68   while (my ($name, $value) = each %options) {
69     next unless $name;
70     next if $_valueless_attributes{$name} && !$value;
71     $value = '' if !defined($value);
72     push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
73   }
74
75   return @result ? ' ' . join(' ', @result) : '';
76 }
77
78 sub html_tag {
79   my ($self, $tag, $content, @slurp) = @_;
80   my $attributes = $self->attributes(@slurp);
81
82   return "<${tag}${attributes}>" unless defined($content);
83   return "<${tag}${attributes}>${content}</${tag}>";
84 }
85
86 sub select_tag {
87   my $self            = shift;
88   my $name            = shift;
89   my $options_str     = shift;
90   my %attributes      = _hashify(@_);
91
92   $attributes{id}   ||= $self->name_to_id($name);
93   $options_str        = $self->options_for_select($options_str) if ref $options_str;
94
95   return $self->html_tag('select', $options_str, %attributes, name => $name);
96 }
97
98 sub textarea_tag {
99   my ($self, $name, $content, @slurp) = @_;
100   my %attributes      = _hashify(@slurp);
101
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) : '';
106
107   return $self->html_tag('textarea', $content, %attributes, name => $name);
108 }
109
110 sub checkbox_tag {
111   my ($self, $name, @slurp) = @_;
112   my %attributes       = _hashify(@slurp);
113
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};
118
119   if ($attributes{checked}) {
120     $attributes{checked} = 'checked';
121   } else {
122     delete $attributes{checked};
123   }
124
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;
128
129   return $code;
130 }
131
132 sub radio_button_tag {
133   my $self             = shift;
134   my $name             = shift;
135   my %attributes       = _hashify(@_);
136
137   $attributes{value}   = 1 unless defined $attributes{value};
138   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
139   my $label            = delete $attributes{label};
140
141   if ($attributes{checked}) {
142     $attributes{checked} = 'checked';
143   } else {
144     delete $attributes{checked};
145   }
146
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;
149
150   return $code;
151 }
152
153 sub input_tag {
154   my ($self, $name, $value, @slurp) = @_;
155   my %attributes      = _hashify(@slurp);
156
157   $attributes{id}   ||= $self->name_to_id($name);
158   $attributes{type} ||= 'text';
159
160   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
161 }
162
163 sub hidden_tag {
164   return shift->input_tag(@_, type => 'hidden');
165 }
166
167 sub div_tag {
168   my ($self, $content, @slurp) = @_;
169   return $self->html_tag('div', $content, @slurp);
170 }
171
172 sub ul_tag {
173   my ($self, $content, @slurp) = @_;
174   return $self->html_tag('ul', $content, @slurp);
175 }
176
177 sub li_tag {
178   my ($self, $content, @slurp) = @_;
179   return $self->html_tag('li', $content, @slurp);
180 }
181
182 sub link {
183   my ($self, $href, $content, @slurp) = @_;
184   my %params = _hashify(@slurp);
185
186   $href ||= '#';
187
188   return $self->html_tag('a', $content, %params, href => $href);
189 }
190
191 sub submit_tag {
192   my ($self, $name, $value, @slurp) = @_;
193   my %attributes = _hashify(@slurp);
194
195   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
196
197   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
198 }
199
200 sub button_tag {
201   my ($self, $onclick, $value, @slurp) = @_;
202   my %attributes = _hashify(@slurp);
203
204   $attributes{id}   ||= $self->name_to_id($attributes{name}) if $attributes{name};
205   $attributes{type} ||= 'button';
206
207   return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
208 }
209
210 sub options_for_select {
211   my $self            = shift;
212   my $collection      = shift;
213   my %options         = _hashify(@_);
214
215   my $value_key       = $options{value} || 'id';
216   my $title_key       = $options{title} || $value_key;
217
218   my $value_sub       = $options{value_sub};
219   my $title_sub       = $options{title_sub};
220
221   my $value_title_sub = $options{value_title_sub};
222
223   my %selected        = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
224
225   my $access = sub {
226     my ($element, $index, $key, $sub) = @_;
227     my $ref = ref $element;
228     return  $sub            ? $sub->($element)
229          : !$ref            ? $element
230          :  $ref eq 'ARRAY' ? $element->[$index]
231          :  $ref eq 'HASH'  ? $element->{$key}
232          :                    $element->$key;
233   };
234
235   my @elements = ();
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),
241     )
242   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
243
244   my $code = '';
245   foreach my $result (@elements) {
246     my %attributes = ( value => $result->[0] );
247     $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
248
249     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
250   }
251
252   return $code;
253 }
254
255 sub javascript {
256   my ($self, $data) = @_;
257   return $self->html_tag('script', $data, type => 'text/javascript');
258 }
259
260 sub stylesheet_tag {
261   my $self = shift;
262   my $code = '';
263
264   foreach my $file (@_) {
265     $file .= '.css'        unless $file =~ m/\.css$/;
266     $file  = "css/${file}" unless $file =~ m|/|;
267
268     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
269   }
270
271   return $code;
272 }
273
274 sub date_tag {
275   my ($self, $name, $value, @slurp) = @_;
276   my %params   = _hashify(@slurp);
277   my $name_e   = _H($name);
278   my $seq      = _tag_id();
279   my $datefmt  = apply {
280     s/d+/\%d/gi;
281     s/m+/\%m/gi;
282     s/y+/\%Y/gi;
283   } $::myconfig{"dateformat"};
284
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;
288
289   $self->input_tag($name, $str_value,
290     id     => $name_e,
291     size   => 11,
292     title  => _H($::myconfig{dateformat}),
293     onBlur => 'check_right_date_format(this)',
294     ($onchange ? (
295     onChange => $onchange,
296     ) : ()),
297     %params,
298   ) . ((!$params{no_cal} && !$params{readonly}) ?
299   $self->html_tag('img', undef,
300     src    => 'image/calendar.png',
301     alt    => $::locale->text('Calendar'),
302     id     => "trigger$seq",
303     title  => _H($::myconfig{dateformat}),
304     %params,
305   ) .
306   $self->javascript(
307     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
308   ) : '');
309 }
310
311 sub customer_picker {
312   my ($self, $name, $value, %params) = @_;
313   my $name_e    = _H($name);
314
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) {
321       \$.ajax({
322         url: 'controller.pl?action=Customer/ajax_autocomplete',
323         dataType: "json",
324         data: {
325           column: column,
326           term: req.term,
327           current: function() { \$('#$name_e').val() },
328           obsolete: 0,
329         },
330         success: function (data){ rsp(data) }
331       });
332     },
333     limit: 20,
334     delay: 50,
335     select: function(event, ui) {
336       \$('#$name_e').val(ui.item.id);
337       \$('#$name_e\_name').val(ui.item.name);
338     },
339   })});
340 }
341 autocomplete_customer('#$name_e\_name');
342 JS
343 }
344
345 sub javascript_tag {
346   my $self = shift;
347   my $code = '';
348
349   foreach my $file (@_) {
350     $file .= '.js'        unless $file =~ m/\.js$/;
351     $file  = "js/${file}" unless $file =~ m|/|;
352
353     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
354   }
355
356   return $code;
357 }
358
359 sub tabbed {
360   my ($self, $tabs, @slurp) = @_;
361   my %params   = _hashify(@slurp);
362   my $id       = $params{id} || 'tab_' . _tag_id();
363
364   $params{selected} *= 1;
365
366   die 'L.tabbed needs an arrayred of tabs for first argument'
367     unless ref $tabs eq 'ARRAY';
368
369   my (@header, @blocks);
370   for my $i (0..$#$tabs) {
371     my $tab = $tabs->[$i];
372
373     next if $tab eq '';
374
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') : ())
380     );
381     push @blocks, $self->div_tag($tab->{data},
382       id => $tab_id, class => 'tabcontent');
383   }
384
385   return '' unless @header;
386   return $self->ul_tag(
387     join('', @header), id => $id, class => 'shadetabs'
388   ) .
389   $self->div_tag(
390     join('', @blocks), class => 'tabcontentstyle'
391   ) .
392   $self->javascript(
393     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
394     qq|$id.setselectedClassTarget("link");$id.init();|
395   );
396 }
397
398 sub tab {
399   my ($self, $name, $src, @slurp) = @_;
400   my %params = _hashify(@slurp);
401
402   $params{method} ||= 'process';
403
404   return () if defined $params{if} && !$params{if};
405
406   my $data;
407   if ($params{method} eq 'raw') {
408     $data = $src;
409   } elsif ($params{method} eq 'process') {
410     $data = $self->_context->process($src, %{ $params{args} || {} });
411   } else {
412     die "unknown tag method '$params{method}'";
413   }
414
415   return () unless $data;
416
417   return +{ name => $name, data => $data };
418 }
419
420 sub areainput_tag {
421   my ($self, $name, $value, @slurp) = @_;
422   my %attributes      = _hashify(@slurp);
423
424   my ($rows, $cols);
425   my $min  = delete $attributes{min_rows} || 1;
426
427   if (exists $attributes{cols}) {
428     $cols = delete $attributes{cols};
429     $rows = $::form->numtextrows($value, $cols);
430   } else {
431     $rows = delete $attributes{rows} || 1;
432   }
433
434   return $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) : ()));
437 }
438
439 sub multiselect2side {
440   my ($self, $id, @slurp) = @_;
441   my %params              = _hashify(@slurp);
442
443   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
444   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
445   $params{moveOptions}    = 'false';
446
447   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
448   my $code                = <<EOCODE;
449 <script type="text/javascript">
450   \$().ready(function() {
451     \$('#${id}').multiselect2side({ ${vars} });
452   });
453 </script>
454 EOCODE
455
456   return $code;
457 }
458
459 sub sortable_element {
460   my ($self, $selector, @slurp) = @_;
461   my %params                    = _hashify(@slurp);
462
463   my %attributes = ( distance => 5,
464                      helper   => <<'JAVASCRIPT' );
465     function(event, ui) {
466       ui.children().each(function() {
467         $(this).width($(this).width());
468       });
469       return ui;
470     }
471 JAVASCRIPT
472
473   my $stop_event = '';
474
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}_', ''); })";
479
480     $stop_event = <<JAVASCRIPT;
481         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
482 JAVASCRIPT
483   }
484
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');
489 JAVASCRIPT
490   }
491
492   if ($stop_event) {
493     $attributes{stop} = <<JAVASCRIPT;
494       function(event, ui) {
495         ${stop_event}
496         return ui;
497       }
498 JAVASCRIPT
499   }
500
501   $params{handle}     = '.dragdrop' unless exists $params{handle};
502   $attributes{handle} = "'$params{handle}'" if $params{handle};
503
504   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
505
506   my $code = <<JAVASCRIPT;
507 <script type="text/javascript">
508   \$(function() {
509     \$( "${selector}" ).sortable({ ${attr_str} })
510   });
511 </script>
512 JAVASCRIPT
513
514   return $code;
515 }
516
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');
523
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')
527 }
528
529 sub dump {
530   my $self = shift;
531   require Data::Dumper;
532   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
533 }
534
535 1;
536
537 __END__
538
539 =head1 NAME
540
541 SL::Templates::Plugin::L -- Layouting / tag generation
542
543 =head1 SYNOPSIS
544
545 Usage from a template:
546
547   [% USE L %]
548
549   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
550
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')) %]
554
555 =head1 DESCRIPTION
556
557 A module modeled a bit after Rails' ActionView helpers. Several small
558 functions that create HTML tags from various kinds of data sources.
559
560 =head1 FUNCTIONS
561
562 =head2 LOW-LEVEL FUNCTIONS
563
564 =over 4
565
566 =item C<name_to_id $name>
567
568 Converts a name to a HTML id by replacing various characters.
569
570 =item C<attributes %items>
571
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.
575
576 =item C<html_tag $tag_name, $content_string, %attributes>
577
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.
582
583 C<$content_string> is not HTML escaped.
584
585 =back
586
587 =head2 HIGH-LEVEL FUNCTIONS
588
589 =over 4
590
591 =item C<select_tag $name, $options_string, %attributes>
592
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)>.
596
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>
600 automatically.
601
602 =item C<input_tag $name, $value, %attributes>
603
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)>.
607
608 =item C<hidden_tag $name, $value, %attributes>
609
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)>.
613
614 =item C<submit_tag $name, $value, %attributes>
615
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)>.
619
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.
624
625 =item C<textarea_tag $name, $value, %attributes>
626
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)>.
630
631 =item C<checkbox_tag $name, %attributes>
632
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>.
636
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
639 that case.
640
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.
644
645 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
646
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>.
652
653 =item C<radio_button_tag $name, %attributes>
654
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)>.
658
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
661 that case.
662
663 =item C<javascript_tag $file1, $file2, $file3...>
664
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.
669
670 =item C<stylesheet_tag $file1, $file2, $file3...>
671
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
675 contain a slash.
676
677 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
678
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>.
684
685 =item C<tabbed \@tab, %attributes>
686
687 Will create a tabbed area. The tabs should be created with the helper function
688 C<tab>. Example:
689
690   [% L.tabbed([
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),
693   ]) %]
694
695 An optional attribute is C<selected>, which accepts the ordinal of a tab which
696 should be selected by default.
697
698 =item C<areainput_tag $name, $content, %PARAMS>
699
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.
704
705 You can force input by setting rows to 1, and you can force textarea by setting
706 rows to anything >1.
707
708 =item C<multiselect2side $id, %params>
709
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
713 entries:
714
715 =over 2
716
717 =item C<labelsx>
718
719 The label of the list of available options. Defaults to the
720 translation of 'Available'.
721
722 =item C<labeldx>
723
724 The label of the list of selected options. Defaults to the
725 translation of 'Selected'.
726
727 =back
728
729 =item C<sortable_element $selector, %params>
730
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.
735
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>.
738
739 C<%params> can contain the following entries:
740
741 =over 2
742
743 =item C<url>
744
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.
748
749 =item C<with>
750
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}>.
757
758 =item C<as>
759
760 Sets the POST parameter name for AJAX request after dropping an
761 element (see C<$params{with}>).
762
763 =item C<handle>
764
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
770 not work.
771
772 =item C<dont_recolor>
773
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.
777
778 =back
779
780 Example:
781
782   <script type="text/javascript" src="js/jquery-ui.js"></script>
783
784   <table id="thing_list">
785     <thead>
786       <tr><td>This</td><td>That</td></tr>
787     </thead>
788     <tbody>
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>
792     </tbody>
793   <table>
794
795   [% L.sortable_element('#thing_list tbody',
796                         url          => 'controller.pl?action=SystemThings/reorder',
797                         with         => 'thingy',
798                         as           => 'thing_ids',
799                         recolor_rows => 1) %]
800
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 ]>.
805
806 =item C<dump REF>
807
808 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
809
810 =back
811
812 =head2 CONVERSION FUNCTIONS
813
814 =over 4
815
816 =item C<options_for_select \@collection, %options>
817
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:
822
823 =over 12
824
825 =item 1. An array reference with at least two elements. The first element is
826 the value, the second element is its title.
827
828 =item 2. A scalar. The scalar is both the value and the title.
829
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.
833
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
837 respectively.
838
839 =back
840
841 For cases 3 and 4 C<$options{value}> defaults to C<id> and
842 C<$options{title}> defaults to C<$options{value}>.
843
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
847 title.
848
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.
852
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
856 an empty string.
857
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
860 selected.
861
862 =item C<tab, description, target, %PARAMS>
863
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.
869
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>:
873
874   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
875
876 =back
877
878 =head1 MODULE AUTHORS
879
880 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
881
882 L<http://linet-services.de>