be4b0a65a239a621743cb47f65545320a4221c6b
[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 $str_value = blessed $value ? $value->to_lxoffice : $value;
287
288   $self->input_tag($name, $str_value,
289     id     => $name_e,
290     size   => 11,
291     title  => _H($::myconfig{dateformat}),
292     onBlur => 'check_right_date_format(this)',
293     %params,
294   ) . ((!$params{no_cal}) ?
295   $self->html_tag('img', undef,
296     src    => 'image/calendar.png',
297     alt    => $::locale->text('Calendar'),
298     id     => "trigger$seq",
299     title  => _H($::myconfig{dateformat}),
300     %params,
301   ) .
302   $self->javascript(
303     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
304   ) : '');
305 }
306
307 sub customer_picker {
308   my ($self, $name, $value, %params) = @_;
309   my $name_e    = _H($name);
310
311   $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
312   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
313   $self->javascript(<<JS);
314 function autocomplete_customer (selector, column) {
315   \$(function(){ \$(selector).autocomplete({
316     source: function(req, rsp) {
317       \$.ajax({
318         url: 'controller.pl?action=Customer/ajax_autocomplete',
319         dataType: "json",
320         data: {
321           column: column,
322           term: req.term,
323           current: function() { \$('#$name_e').val() },
324           obsolete: 0,
325         },
326         success: function (data){ rsp(data) }
327       });
328     },
329     limit: 20,
330     delay: 50,
331     select: function(event, ui) {
332       \$('#$name_e').val(ui.item.id);
333       \$('#$name_e\_name').val(ui.item.name);
334     },
335   })});
336 }
337 autocomplete_customer('#$name_e\_name');
338 JS
339 }
340
341 sub javascript_tag {
342   my $self = shift;
343   my $code = '';
344
345   foreach my $file (@_) {
346     $file .= '.js'        unless $file =~ m/\.js$/;
347     $file  = "js/${file}" unless $file =~ m|/|;
348
349     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
350   }
351
352   return $code;
353 }
354
355 sub tabbed {
356   my ($self, $tabs, @slurp) = @_;
357   my %params   = _hashify(@slurp);
358   my $id       = $params{id} || 'tab_' . _tag_id();
359
360   $params{selected} *= 1;
361
362   die 'L.tabbed needs an arrayred of tabs for first argument'
363     unless ref $tabs eq 'ARRAY';
364
365   my (@header, @blocks);
366   for my $i (0..$#$tabs) {
367     my $tab = $tabs->[$i];
368
369     next if $tab eq '';
370
371     my $selected = $params{selected} == $i;
372     my $tab_id   = "__tab_id_$i";
373     push @header, $self->li_tag(
374       $self->link('', $tab->{name}, rel => $tab_id),
375         ($selected ? (class => 'selected') : ())
376     );
377     push @blocks, $self->div_tag($tab->{data},
378       id => $tab_id, class => 'tabcontent');
379   }
380
381   return '' unless @header;
382   return $self->ul_tag(
383     join('', @header), id => $id, class => 'shadetabs'
384   ) .
385   $self->div_tag(
386     join('', @blocks), class => 'tabcontentstyle'
387   ) .
388   $self->javascript(
389     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
390     qq|$id.setselectedClassTarget("link");$id.init();|
391   );
392 }
393
394 sub tab {
395   my ($self, $name, $src, @slurp) = @_;
396   my %params = _hashify(@slurp);
397
398   $params{method} ||= 'process';
399
400   return () if defined $params{if} && !$params{if};
401
402   my $data;
403   if ($params{method} eq 'raw') {
404     $data = $src;
405   } elsif ($params{method} eq 'process') {
406     $data = $self->_context->process($src, %{ $params{args} || {} });
407   } else {
408     die "unknown tag method '$params{method}'";
409   }
410
411   return () unless $data;
412
413   return +{ name => $name, data => $data };
414 }
415
416 sub areainput_tag {
417   my ($self, $name, $value, @slurp) = @_;
418   my %attributes      = _hashify(@slurp);
419
420   my $rows = delete $attributes{rows}     || 1;
421   my $min  = delete $attributes{min_rows} || 1;
422
423   return $rows > 1
424     ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
425     : $self->input_tag($name, $value, %attributes);
426 }
427
428 sub multiselect2side {
429   my ($self, $id, @slurp) = @_;
430   my %params              = _hashify(@slurp);
431
432   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
433   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
434   $params{moveOptions}    = 'false';
435
436   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
437   my $code                = <<EOCODE;
438 <script type="text/javascript">
439   \$().ready(function() {
440     \$('#${id}').multiselect2side({ ${vars} });
441   });
442 </script>
443 EOCODE
444
445   return $code;
446 }
447
448 sub sortable_element {
449   my ($self, $selector, @slurp) = @_;
450   my %params                    = _hashify(@slurp);
451
452   my %attributes = ( distance => 5,
453                      helper   => <<'JAVASCRIPT' );
454     function(event, ui) {
455       ui.children().each(function() {
456         $(this).width($(this).width());
457       });
458       return ui;
459     }
460 JAVASCRIPT
461
462   my $stop_event = '';
463
464   if ($params{url} && $params{with}) {
465     my $as      = $params{as} || $params{with};
466     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
467     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
468
469     $stop_event = <<JAVASCRIPT;
470         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
471 JAVASCRIPT
472   }
473
474   if (!$params{dont_recolor}) {
475     $stop_event .= <<JAVASCRIPT;
476         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
477         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
478 JAVASCRIPT
479   }
480
481   if ($stop_event) {
482     $attributes{stop} = <<JAVASCRIPT;
483       function(event, ui) {
484         ${stop_event}
485         return ui;
486       }
487 JAVASCRIPT
488   }
489
490   $params{handle}     = '.dragdrop' unless exists $params{handle};
491   $attributes{handle} = "'$params{handle}'" if $params{handle};
492
493   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
494
495   my $code = <<JAVASCRIPT;
496 <script type="text/javascript">
497   \$(function() {
498     \$( "${selector}" ).sortable({ ${attr_str} })
499   });
500 </script>
501 JAVASCRIPT
502
503   return $code;
504 }
505
506 sub online_help_tag {
507   my ($self, $tag, @slurp) = @_;
508   my %params               = _hashify(@slurp);
509   my $cc                   = $::myconfig{countrycode};
510   my $file                 = "doc/online/$cc/$tag.html";
511   my $text                 = $params{text} || $::locale->text('Help');
512
513   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
514   return unless -f $file;
515   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
516 }
517
518 sub dump {
519   my $self = shift;
520   require Data::Dumper;
521   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
522 }
523
524 1;
525
526 __END__
527
528 =head1 NAME
529
530 SL::Templates::Plugin::L -- Layouting / tag generation
531
532 =head1 SYNOPSIS
533
534 Usage from a template:
535
536   [% USE L %]
537
538   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
539
540   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
541                                                       { direction => 'right', display => 'To the right' } ],
542                                                     value => 'direction', title => 'display', default => 'right')) %]
543
544 =head1 DESCRIPTION
545
546 A module modeled a bit after Rails' ActionView helpers. Several small
547 functions that create HTML tags from various kinds of data sources.
548
549 =head1 FUNCTIONS
550
551 =head2 LOW-LEVEL FUNCTIONS
552
553 =over 4
554
555 =item C<name_to_id $name>
556
557 Converts a name to a HTML id by replacing various characters.
558
559 =item C<attributes %items>
560
561 Creates a string from all elements in C<%items> suitable for usage as
562 HTML tag attributes. Keys and values are HTML escaped even though keys
563 must not contain non-ASCII characters for browsers to accept them.
564
565 =item C<html_tag $tag_name, $content_string, %attributes>
566
567 Creates an opening and closing HTML tag for C<$tag_name> and puts
568 C<$content_string> between the two. If C<$content_string> is undefined
569 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
570 are key/value pairs added to the opening tag.
571
572 C<$content_string> is not HTML escaped.
573
574 =back
575
576 =head2 HIGH-LEVEL FUNCTIONS
577
578 =over 4
579
580 =item C<select_tag $name, $options_string, %attributes>
581
582 Creates a HTML 'select' tag named C<$name> with the contents
583 C<$options_string> and with arbitrary HTML attributes from
584 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
585
586 The C<$options_string> is usually created by the
587 L</options_for_select> function. If C<$options_string> is an array
588 reference then it will be passed to L</options_for_select>
589 automatically.
590
591 =item C<input_tag $name, $value, %attributes>
592
593 Creates a HTML 'input type=text' tag named C<$name> with the value
594 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
595 tag's C<id> defaults to C<name_to_id($name)>.
596
597 =item C<hidden_tag $name, $value, %attributes>
598
599 Creates a HTML 'input type=hidden' tag named C<$name> with the value
600 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
601 tag's C<id> defaults to C<name_to_id($name)>.
602
603 =item C<submit_tag $name, $value, %attributes>
604
605 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
606 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
607 tag's C<id> defaults to C<name_to_id($name)>.
608
609 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
610 be added via the C<onclick> handler asking the question given with
611 C<$attributes{confirm}>. If request is only submitted if the user
612 clicks the dialog's ok/yes button.
613
614 =item C<textarea_tag $name, $value, %attributes>
615
616 Creates a HTML 'textarea' tag named C<$name> with the content
617 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 =item C<checkbox_tag $name, %attributes>
621
622 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
623 HTML attributes from C<%attributes>. The tag's C<id> defaults to
624 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
625
626 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
627 created with said C<label>. No attribute named C<label> is created in
628 that case.
629
630 If C<%attributes> contains a key C<checkall> then the value is taken as a
631 JQuery selector and clicking this checkbox will also toggle all checkboxes
632 matching the selector.
633
634 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
635
636 Creates a date input field, with an attached javascript that will open a
637 calendar on click. The javascript ist by default anchoered at the bottom right
638 sight. This can be overridden with C<cal_align>, see Calendar documentation for
639 the details, usually you'll want a two letter abbreviation of the alignment.
640 Right + Bottom becomes C<BL>.
641
642 =item C<radio_button_tag $name, %attributes>
643
644 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
645 HTML attributes from C<%attributes>. The tag's C<value> defaults to
646 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
647
648 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
649 created with said C<label>. No attribute named C<label> is created in
650 that case.
651
652 =item C<javascript_tag $file1, $file2, $file3...>
653
654 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
655 tag for each file name parameter passed. Each file name will be
656 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
657 doesn't contain a slash.
658
659 =item C<stylesheet_tag $file1, $file2, $file3...>
660
661 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
662 for each file name parameter passed. Each file name will be postfixed
663 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
664 contain a slash.
665
666 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
667
668 Creates a date input field, with an attached javascript that will open a
669 calendar on click. The javascript ist by default anchoered at the bottom right
670 sight. This can be overridden with C<cal_align>, see Calendar documentation for
671 the details, usually you'll want a two letter abbreviation of the alignment.
672 Right + Bottom becomes C<BL>.
673
674 =item C<tabbed \@tab, %attributes>
675
676 Will create a tabbed area. The tabs should be created with the helper function
677 C<tab>. Example:
678
679   [% L.tabbed([
680     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
681     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
682   ]) %]
683
684 An optional attribute is C<selected>, which accepts the ordinal of a tab which
685 should be selected by default.
686
687 =item C<areainput_tag $name, $content, %PARAMS>
688
689 Creates a generic input tag or textarea tag, depending on content size. The
690 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
691 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
692
693 You can force input by setting rows to 1, and you can force textarea by setting
694 rows to anything >1.
695
696 =item C<multiselect2side $id, %params>
697
698 Creates a JavaScript snippet calling the jQuery function
699 C<multiselect2side> on the select control with the ID C<$id>. The
700 select itself is not created. C<%params> can contain the following
701 entries:
702
703 =over 2
704
705 =item C<labelsx>
706
707 The label of the list of available options. Defaults to the
708 translation of 'Available'.
709
710 =item C<labeldx>
711
712 The label of the list of selected options. Defaults to the
713 translation of 'Selected'.
714
715 =back
716
717 =item C<sortable_element $selector, %params>
718
719 Makes the children of the DOM element C<$selector> (a jQuery selector)
720 sortable with the I<jQuery UI Selectable> library. The children can be
721 dragged & dropped around. After dropping an element an URL can be
722 postet to with the element IDs of the sorted children.
723
724 If this is used then the JavaScript file C<js/jquery-ui.js> must be
725 included manually as well as it isn't loaded via C<$::form-gt;header>.
726
727 C<%params> can contain the following entries:
728
729 =over 2
730
731 =item C<url>
732
733 The URL to POST an AJAX request to after a dragged element has been
734 dropped. The AJAX request's return value is ignored. If given then
735 C<$params{with}> must be given as well.
736
737 =item C<with>
738
739 A string that is interpreted as the prefix of the children's ID. Upon
740 POSTing the result each child whose ID starts with C<$params{with}> is
741 considered. The prefix and the following "_" is removed from the
742 ID. The remaining parts of the IDs of those children are posted as a
743 single array parameter. The array parameter's name is either
744 C<$params{as}> or, missing that, C<$params{with}>.
745
746 =item C<as>
747
748 Sets the POST parameter name for AJAX request after dropping an
749 element (see C<$params{with}>).
750
751 =item C<handle>
752
753 An optional jQuery selector specifying which part of the child element
754 is dragable. If the parameter is not given then it defaults to
755 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
756 parameter is set and empty then the whole child element is dragable,
757 and clicks through to underlying elements like inputs or links might
758 not work.
759
760 =item C<dont_recolor>
761
762 If trueish then the children will not be recolored. The default is to
763 recolor the children by setting the class C<listrow0> on odd and
764 C<listrow1> on even entries.
765
766 =back
767
768 Example:
769
770   <script type="text/javascript" src="js/jquery-ui.js"></script>
771
772   <table id="thing_list">
773     <thead>
774       <tr><td>This</td><td>That</td></tr>
775     </thead>
776     <tbody>
777       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
778       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
779       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
780     </tbody>
781   <table>
782
783   [% L.sortable_element('#thing_list tbody',
784                         url          => 'controller.pl?action=SystemThings/reorder',
785                         with         => 'thingy',
786                         as           => 'thing_ids',
787                         recolor_rows => 1) %]
788
789 After dropping e.g. the third element at the top of the list a POST
790 request would be made to the C<reorder> action of the C<SystemThings>
791 controller with a single parameter called C<thing_ids> -- an array
792 containing the values C<[ 6, 2, 15 ]>.
793
794 =item C<dump REF>
795
796 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
797
798 =back
799
800 =head2 CONVERSION FUNCTIONS
801
802 =over 4
803
804 =item C<options_for_select \@collection, %options>
805
806 Creates a string suitable for a HTML 'select' tag consisting of one
807 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
808 to use and the title to display are extracted from the elements in
809 C<\@collection>. Each element can be one of four things:
810
811 =over 12
812
813 =item 1. An array reference with at least two elements. The first element is
814 the value, the second element is its title.
815
816 =item 2. A scalar. The scalar is both the value and the title.
817
818 =item 3. A hash reference. In this case C<%options> must contain
819 I<value> and I<title> keys that name the keys in the element to use
820 for the value and title respectively.
821
822 =item 4. A blessed reference. In this case C<%options> must contain
823 I<value> and I<title> keys that name functions called on the blessed
824 reference whose return values are used as the value and title
825 respectively.
826
827 =back
828
829 For cases 3 and 4 C<$options{value}> defaults to C<id> and
830 C<$options{title}> defaults to C<$options{value}>.
831
832 In addition to pure keys/method you can also provide coderefs as I<value_sub>
833 and/or I<title_sub>. If present, these take precedence over keys or methods,
834 and are called with the element as first argument. It must return the value or
835 title.
836
837 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
838 precedence over each individual sub. It will only be called once for each
839 element and must return a list of value and title.
840
841 If the option C<with_empty> is set then an empty element (value
842 C<undef>) will be used as the first element. The title to display for
843 this element can be set with the option C<empty_title> and defaults to
844 an empty string.
845
846 The option C<default> can be either a scalar or an array reference
847 containing the values of the options which should be set to be
848 selected.
849
850 =item C<tab, description, target, %PARAMS>
851
852 Creates a tab for C<tabbed>. The description will be used as displayed name.
853 The target should be a block or template that can be processed. C<tab> supports
854 a C<method> parameter, which can override the process method to apply target.
855 C<method => 'raw'> will just include the given text as is. I was too lazy to
856 implement C<include> properly.
857
858 Also an C<if> attribute is supported, so that tabs can be suppressed based on
859 some occasion. In this case the supplied block won't even get processed, and
860 the resulting tab will get ignored by C<tabbed>:
861
862   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
863
864 =back
865
866 =head1 MODULE AUTHORS
867
868 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
869
870 L<http://linet-services.de>