customer_picker
[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
8 use strict;
9
10 { # This will give you an id for identifying html tags and such.
11   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
12   # Do not use these id's to store information across requests.
13 my $_id_sequence = int rand 1e7;
14 sub _tag_id {
15   return $_id_sequence = ($_id_sequence + 1) % 1e7;
16 }
17 }
18
19 sub _H {
20   my $string = shift;
21   return $::locale->quote_special_chars('HTML', $string);
22 }
23
24 sub _J {
25   my $string =  "" . shift;
26   $string    =~ s/\"/\\\"/g;
27   return $string;
28 }
29
30 sub _hashify {
31   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
32 }
33
34 sub new {
35   my ($class, $context, @args) = @_;
36
37   return bless {
38     CONTEXT => $context,
39   }, $class;
40 }
41
42 sub _context {
43   die 'not an accessor' if @_ > 1;
44   return $_[0]->{CONTEXT};
45 }
46
47 sub name_to_id {
48   my $self =  shift;
49   my $name =  shift;
50
51   $name    =~ s/[^\w_]/_/g;
52   $name    =~ s/_+/_/g;
53
54   return $name;
55 }
56
57 sub attributes {
58   my ($self, @slurp)    = @_;
59   my %options = _hashify(@slurp);
60
61   my @result = ();
62   while (my ($name, $value) = each %options) {
63     next unless $name;
64     next if $name eq 'disabled' && !$value;
65     $value = '' if !defined($value);
66     push @result, _H($name) . '="' . _H($value) . '"';
67   }
68
69   return @result ? ' ' . join(' ', @result) : '';
70 }
71
72 sub html_tag {
73   my ($self, $tag, $content, @slurp) = @_;
74   my $attributes = $self->attributes(@slurp);
75
76   return "<${tag}${attributes}/>" unless defined($content);
77   return "<${tag}${attributes}>${content}</${tag}>";
78 }
79
80 sub select_tag {
81   my $self            = shift;
82   my $name            = shift;
83   my $options_str     = shift;
84   my %attributes      = _hashify(@_);
85
86   $attributes{id}   ||= $self->name_to_id($name);
87   $options_str        = $self->options_for_select($options_str) if ref $options_str;
88
89   return $self->html_tag('select', $options_str, %attributes, name => $name);
90 }
91
92 sub textarea_tag {
93   my ($self, $name, $content, @slurp) = @_;
94   my %attributes      = _hashify(@slurp);
95
96   $attributes{id}   ||= $self->name_to_id($name);
97   $content            = $content ? _H($content) : '';
98
99   return $self->html_tag('textarea', $content, %attributes, name => $name);
100 }
101
102 sub checkbox_tag {
103   my ($self, $name, @slurp) = @_;
104   my %attributes       = _hashify(@slurp);
105
106   $attributes{id}    ||= $self->name_to_id($name);
107   $attributes{value}   = 1 unless defined $attributes{value};
108   my $label            = delete $attributes{label};
109   my $checkall         = delete $attributes{checkall};
110
111   if ($attributes{checked}) {
112     $attributes{checked} = 'checked';
113   } else {
114     delete $attributes{checked};
115   }
116
117   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
118   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
119   $code    .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
120
121   return $code;
122 }
123
124 sub radio_button_tag {
125   my $self             = shift;
126   my $name             = shift;
127   my %attributes       = _hashify(@_);
128
129   $attributes{value}   = 1 unless defined $attributes{value};
130   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
131   my $label            = delete $attributes{label};
132
133   if ($attributes{checked}) {
134     $attributes{checked} = 'checked';
135   } else {
136     delete $attributes{checked};
137   }
138
139   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
140   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
141
142   return $code;
143 }
144
145 sub input_tag {
146   my ($self, $name, $value, @slurp) = @_;
147   my %attributes      = _hashify(@slurp);
148
149   $attributes{id}   ||= $self->name_to_id($name);
150   $attributes{type} ||= 'text';
151
152   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
153 }
154
155 sub hidden_tag {
156   return shift->input_tag(@_, type => 'hidden');
157 }
158
159 sub div_tag {
160   my ($self, $content, @slurp) = @_;
161   return $self->html_tag('div', $content, @slurp);
162 }
163
164 sub ul_tag {
165   my ($self, $content, @slurp) = @_;
166   return $self->html_tag('ul', $content, @slurp);
167 }
168
169 sub li_tag {
170   my ($self, $content, @slurp) = @_;
171   return $self->html_tag('li', $content, @slurp);
172 }
173
174 sub link {
175   my ($self, $href, $content, @slurp) = @_;
176   my %params = _hashify(@slurp);
177
178   $href ||= '#';
179
180   return $self->html_tag('a', $content, %params, href => $href);
181 }
182
183 sub submit_tag {
184   my ($self, $name, $value, @slurp) = @_;
185   my %attributes = _hashify(@slurp);
186
187   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
188
189   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
190 }
191
192 sub button_tag {
193   my ($self, $onclick, $value, @slurp) = @_;
194   my %attributes = _hashify(@slurp);
195
196   return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
197 }
198
199 sub options_for_select {
200   my $self            = shift;
201   my $collection      = shift;
202   my %options         = _hashify(@_);
203
204   my $value_key       = $options{value} || 'id';
205   my $title_key       = $options{title} || $value_key;
206
207   my $value_sub       = $options{value_sub};
208   my $title_sub       = $options{title_sub};
209
210   my $value_title_sub = $options{value_title_sub};
211
212   my %selected        = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
213
214   my $access = sub {
215     my ($element, $index, $key, $sub) = @_;
216     my $ref = ref $element;
217     return  $sub            ? $sub->($element)
218          : !$ref            ? $element
219          :  $ref eq 'ARRAY' ? $element->[$index]
220          :  $ref eq 'HASH'  ? $element->{$key}
221          :                    $element->$key;
222   };
223
224   my @elements = ();
225   push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
226   push @elements, map [
227     $value_title_sub ? @{ $value_title_sub->($_) } : (
228       $access->($_, 0, $value_key, $value_sub),
229       $access->($_, 1, $title_key, $title_sub),
230     )
231   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
232
233   my $code = '';
234   foreach my $result (@elements) {
235     my %attributes = ( value => $result->[0] );
236     $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
237
238     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
239   }
240
241   return $code;
242 }
243
244 sub javascript {
245   my ($self, $data) = @_;
246   return $self->html_tag('script', $data, type => 'text/javascript');
247 }
248
249 sub stylesheet_tag {
250   my $self = shift;
251   my $code = '';
252
253   foreach my $file (@_) {
254     $file .= '.css'        unless $file =~ m/\.css$/;
255     $file  = "css/${file}" unless $file =~ m|/|;
256
257     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
258   }
259
260   return $code;
261 }
262
263 sub date_tag {
264   my ($self, $name, $value, @slurp) = @_;
265   my %params   = _hashify(@slurp);
266   my $name_e   = _H($name);
267   my $seq      = _tag_id();
268   my $datefmt  = apply {
269     s/d+/\%d/gi;
270     s/m+/\%m/gi;
271     s/y+/\%Y/gi;
272   } $::myconfig{"dateformat"};
273
274   $params{cal_align} ||= 'BR';
275
276   $self->input_tag($name, $value,
277     id     => $name_e,
278     size   => 11,
279     title  => _H($::myconfig{dateformat}),
280     onBlur => 'check_right_date_format(this)',
281     %params,
282   ) . ((!$params{no_cal}) ?
283   $self->html_tag('img', undef,
284     src    => 'image/calendar.png',
285     id     => "trigger$seq",
286     title  => _H($::myconfig{dateformat}),
287     %params,
288   ) .
289   $self->javascript(
290     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
291   ) : '');
292 }
293
294 sub customer_picker {
295   my ($self, $name, $value, %params) = @_;
296   my $name_e    = _H($name);
297
298   $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
299   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
300   $self->javascript(<<JS);
301 function autocomplete_customer (selector, column) {
302   \$(function(){ \$(selector).autocomplete({
303     source: function(req, rsp) {
304       \$.ajax({
305         url: 'controller.pl?action=Customer/ajax_autocomplete',
306         dataType: "json",
307         data: {
308           column: column,
309           term: req.term,
310           current: function() { \$('#$name_e').val() },
311           obsolete: 0,
312         },
313         success: function (data){ rsp(data) }
314       });
315     },
316     limit: 20,
317     delay: 50,
318     select: function(event, ui) {
319       \$('#$name_e').val(ui.item.id);
320       \$('#$name_e\_name').val(ui.item.name);
321     },
322   })});
323 }
324 autocomplete_customer('#$name_e\_name');
325 JS
326 }
327
328 sub javascript_tag {
329   my $self = shift;
330   my $code = '';
331
332   foreach my $file (@_) {
333     $file .= '.js'        unless $file =~ m/\.js$/;
334     $file  = "js/${file}" unless $file =~ m|/|;
335
336     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
337   }
338
339   return $code;
340 }
341
342 sub tabbed {
343   my ($self, $tabs, @slurp) = @_;
344   my %params   = _hashify(@slurp);
345   my $id       = $params{id} || 'tab_' . _tag_id();
346
347   $params{selected} *= 1;
348
349   die 'L.tabbed needs an arrayred of tabs for first argument'
350     unless ref $tabs eq 'ARRAY';
351
352   my (@header, @blocks);
353   for my $i (0..$#$tabs) {
354     my $tab = $tabs->[$i];
355
356     next if $tab eq '';
357
358     my $selected = $params{selected} == $i;
359     my $tab_id   = "__tab_id_$i";
360     push @header, $self->li_tag(
361       $self->link('', $tab->{name}, rel => $tab_id),
362         ($selected ? (class => 'selected') : ())
363     );
364     push @blocks, $self->div_tag($tab->{data},
365       id => $tab_id, class => 'tabcontent');
366   }
367
368   return '' unless @header;
369   return $self->ul_tag(
370     join('', @header), id => $id, class => 'shadetabs'
371   ) .
372   $self->div_tag(
373     join('', @blocks), class => 'tabcontentstyle'
374   ) .
375   $self->javascript(
376     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
377     qq|$id.setselectedClassTarget("link");$id.init();|
378   );
379 }
380
381 sub tab {
382   my ($self, $name, $src, @slurp) = @_;
383   my %params = _hashify(@slurp);
384
385   $params{method} ||= 'process';
386
387   return () if defined $params{if} && !$params{if};
388
389   my $data;
390   if ($params{method} eq 'raw') {
391     $data = $src;
392   } elsif ($params{method} eq 'process') {
393     $data = $self->_context->process($src, %{ $params{args} || {} });
394   } else {
395     die "unknown tag method '$params{method}'";
396   }
397
398   return () unless $data;
399
400   return +{ name => $name, data => $data };
401 }
402
403 sub areainput_tag {
404   my ($self, $name, $value, @slurp) = @_;
405   my %attributes      = _hashify(@slurp);
406
407   my $rows = delete $attributes{rows}     || 1;
408   my $min  = delete $attributes{min_rows} || 1;
409
410   return $rows > 1
411     ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
412     : $self->input_tag($name, $value, %attributes);
413 }
414
415 sub multiselect2side {
416   my ($self, $id, @slurp) = @_;
417   my %params              = _hashify(@slurp);
418
419   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
420   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
421   $params{moveOptions}    = 'false';
422
423   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
424   my $code                = <<EOCODE;
425 <script type="text/javascript">
426   \$().ready(function() {
427     \$('#${id}').multiselect2side({ ${vars} });
428   });
429 </script>
430 EOCODE
431
432   return $code;
433 }
434
435 sub sortable_element {
436   my ($self, $selector, @slurp) = @_;
437   my %params                    = _hashify(@slurp);
438
439   my %attributes = ( distance => 5,
440                      helper   => <<'JAVASCRIPT' );
441     function(event, ui) {
442       ui.children().each(function() {
443         $(this).width($(this).width());
444       });
445       return ui;
446     }
447 JAVASCRIPT
448
449   my $stop_event = '';
450
451   if ($params{url} && $params{with}) {
452     my $as      = $params{as} || $params{with};
453     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
454     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
455
456     $stop_event = <<JAVASCRIPT;
457         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
458 JAVASCRIPT
459   }
460
461   if (!$params{dont_recolor}) {
462     $stop_event .= <<JAVASCRIPT;
463         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
464         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
465 JAVASCRIPT
466   }
467
468   if ($stop_event) {
469     $attributes{stop} = <<JAVASCRIPT;
470       function(event, ui) {
471         ${stop_event}
472         return ui;
473       }
474 JAVASCRIPT
475   }
476
477   $params{handle}     = '.dragdrop' unless exists $params{handle};
478   $attributes{handle} = "'$params{handle}'" if $params{handle};
479
480   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
481
482   my $code = <<JAVASCRIPT;
483 <script type="text/javascript">
484   \$(function() {
485     \$( "${selector}" ).sortable({ ${attr_str} })
486   });
487 </script>
488 JAVASCRIPT
489
490   return $code;
491 }
492
493 sub online_help_tag {
494   my ($self, $tag, @slurp) = @_;
495   my %params               = _hashify(@slurp);
496   my $cc                   = $::myconfig{countrycode};
497   my $file                 = "doc/online/$cc/$tag.html";
498   my $text                 = $params{text} || $::locale->text('Help');
499
500   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
501   return unless -f $file;
502   return $self->html_tag('a', $text, href => $file, target => '_blank');
503 }
504
505 sub dump {
506   my $self = shift;
507   require Data::Dumper;
508   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
509 }
510
511 1;
512
513 __END__
514
515 =head1 NAME
516
517 SL::Templates::Plugin::L -- Layouting / tag generation
518
519 =head1 SYNOPSIS
520
521 Usage from a template:
522
523   [% USE L %]
524
525   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
526
527   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
528                                                       { direction => 'right', display => 'To the right' } ],
529                                                     value => 'direction', title => 'display', default => 'right')) %]
530
531 =head1 DESCRIPTION
532
533 A module modeled a bit after Rails' ActionView helpers. Several small
534 functions that create HTML tags from various kinds of data sources.
535
536 =head1 FUNCTIONS
537
538 =head2 LOW-LEVEL FUNCTIONS
539
540 =over 4
541
542 =item C<name_to_id $name>
543
544 Converts a name to a HTML id by replacing various characters.
545
546 =item C<attributes %items>
547
548 Creates a string from all elements in C<%items> suitable for usage as
549 HTML tag attributes. Keys and values are HTML escaped even though keys
550 must not contain non-ASCII characters for browsers to accept them.
551
552 =item C<html_tag $tag_name, $content_string, %attributes>
553
554 Creates an opening and closing HTML tag for C<$tag_name> and puts
555 C<$content_string> between the two. If C<$content_string> is undefined
556 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
557 are key/value pairs added to the opening tag.
558
559 C<$content_string> is not HTML escaped.
560
561 =back
562
563 =head2 HIGH-LEVEL FUNCTIONS
564
565 =over 4
566
567 =item C<select_tag $name, $options_string, %attributes>
568
569 Creates a HTML 'select' tag named C<$name> with the contents
570 C<$options_string> and with arbitrary HTML attributes from
571 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
572
573 The C<$options_string> is usually created by the
574 L</options_for_select> function. If C<$options_string> is an array
575 reference then it will be passed to L</options_for_select>
576 automatically.
577
578 =item C<input_tag $name, $value, %attributes>
579
580 Creates a HTML 'input type=text' tag named C<$name> with the value
581 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
582 tag's C<id> defaults to C<name_to_id($name)>.
583
584 =item C<hidden_tag $name, $value, %attributes>
585
586 Creates a HTML 'input type=hidden' tag named C<$name> with the value
587 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
588 tag's C<id> defaults to C<name_to_id($name)>.
589
590 =item C<submit_tag $name, $value, %attributes>
591
592 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
593 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
594 tag's C<id> defaults to C<name_to_id($name)>.
595
596 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
597 be added via the C<onclick> handler asking the question given with
598 C<$attributes{confirm}>. If request is only submitted if the user
599 clicks the dialog's ok/yes button.
600
601 =item C<textarea_tag $name, $value, %attributes>
602
603 Creates a HTML 'textarea' tag named C<$name> with the content
604 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
605 tag's C<id> defaults to C<name_to_id($name)>.
606
607 =item C<checkbox_tag $name, %attributes>
608
609 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
610 HTML attributes from C<%attributes>. The tag's C<id> defaults to
611 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
612
613 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
614 created with said C<label>. No attribute named C<label> is created in
615 that case.
616
617 If C<%attributes> contains a key C<checkall> then the value is taken as a
618 JQuery selector and clicking this checkbox will also toggle all checkboxes
619 matching the selector.
620
621 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
622
623 Creates a date input field, with an attached javascript that will open a
624 calendar on click. The javascript ist by default anchoered at the bottom right
625 sight. This can be overridden with C<cal_align>, see Calendar documentation for
626 the details, usually you'll want a two letter abbreviation of the alignment.
627 Right + Bottom becomes C<BL>.
628
629 =item C<radio_button_tag $name, %attributes>
630
631 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
632 HTML attributes from C<%attributes>. The tag's C<value> defaults to
633 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
634
635 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
636 created with said C<label>. No attribute named C<label> is created in
637 that case.
638
639 =item C<javascript_tag $file1, $file2, $file3...>
640
641 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
642 tag for each file name parameter passed. Each file name will be
643 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
644 doesn't contain a slash.
645
646 =item C<stylesheet_tag $file1, $file2, $file3...>
647
648 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
649 for each file name parameter passed. Each file name will be postfixed
650 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
651 contain a slash.
652
653 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
654
655 Creates a date input field, with an attached javascript that will open a
656 calendar on click. The javascript ist by default anchoered at the bottom right
657 sight. This can be overridden with C<cal_align>, see Calendar documentation for
658 the details, usually you'll want a two letter abbreviation of the alignment.
659 Right + Bottom becomes C<BL>.
660
661 =item C<tabbed \@tab, %attributes>
662
663 Will create a tabbed area. The tabs should be created with the helper function
664 C<tab>. Example:
665
666   [% L.tabbed([
667     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
668     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
669   ]) %]
670
671 An optional attribute is C<selected>, which accepts the ordinal of a tab which
672 should be selected by default.
673
674 =item C<areainput_tag $name, $content, %PARAMS>
675
676 Creates a generic input tag or textarea tag, depending on content size. The
677 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
678 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
679
680 You can force input by setting rows to 1, and you can force textarea by setting
681 rows to anything >1.
682
683 =item C<multiselect2side $id, %params>
684
685 Creates a JavaScript snippet calling the jQuery function
686 C<multiselect2side> on the select control with the ID C<$id>. The
687 select itself is not created. C<%params> can contain the following
688 entries:
689
690 =over 2
691
692 =item C<labelsx>
693
694 The label of the list of available options. Defaults to the
695 translation of 'Available'.
696
697 =item C<labeldx>
698
699 The label of the list of selected options. Defaults to the
700 translation of 'Selected'.
701
702 =back
703
704 =item C<sortable_element $selector, %params>
705
706 Makes the children of the DOM element C<$selector> (a jQuery selector)
707 sortable with the I<jQuery UI Selectable> library. The children can be
708 dragged & dropped around. After dropping an element an URL can be
709 postet to with the element IDs of the sorted children.
710
711 If this is used then the JavaScript file C<js/jquery-ui.js> must be
712 included manually as well as it isn't loaded via C<$::form-gt;header>.
713
714 C<%params> can contain the following entries:
715
716 =over 2
717
718 =item C<url>
719
720 The URL to POST an AJAX request to after a dragged element has been
721 dropped. The AJAX request's return value is ignored. If given then
722 C<$params{with}> must be given as well.
723
724 =item C<with>
725
726 A string that is interpreted as the prefix of the children's ID. Upon
727 POSTing the result each child whose ID starts with C<$params{with}> is
728 considered. The prefix and the following "_" is removed from the
729 ID. The remaining parts of the IDs of those children are posted as a
730 single array parameter. The array parameter's name is either
731 C<$params{as}> or, missing that, C<$params{with}>.
732
733 =item C<as>
734
735 Sets the POST parameter name for AJAX request after dropping an
736 element (see C<$params{with}>).
737
738 =item C<handle>
739
740 An optional jQuery selector specifying which part of the child element
741 is dragable. If the parameter is not given then it defaults to
742 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
743 parameter is set and empty then the whole child element is dragable,
744 and clicks through to underlying elements like inputs or links might
745 not work.
746
747 =item C<dont_recolor>
748
749 If trueish then the children will not be recolored. The default is to
750 recolor the children by setting the class C<listrow0> on odd and
751 C<listrow1> on even entries.
752
753 =back
754
755 Example:
756
757   <script type="text/javascript" src="js/jquery-ui.js"></script>
758
759   <table id="thing_list">
760     <thead>
761       <tr><td>This</td><td>That</td></tr>
762     </thead>
763     <tbody>
764       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
765       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
766       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
767     </tbody>
768   <table>
769
770   [% L.sortable_element('#thing_list tbody',
771                         url          => 'controller.pl?action=SystemThings/reorder',
772                         with         => 'thingy',
773                         as           => 'thing_ids',
774                         recolor_rows => 1) %]
775
776 After dropping e.g. the third element at the top of the list a POST
777 request would be made to the C<reorder> action of the C<SystemThings>
778 controller with a single parameter called C<thing_ids> -- an array
779 containing the values C<[ 6, 2, 15 ]>.
780
781 =item C<dump REF>
782
783 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
784
785 =back
786
787 =head2 CONVERSION FUNCTIONS
788
789 =over 4
790
791 =item C<options_for_select \@collection, %options>
792
793 Creates a string suitable for a HTML 'select' tag consisting of one
794 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
795 to use and the title to display are extracted from the elements in
796 C<\@collection>. Each element can be one of four things:
797
798 =over 12
799
800 =item 1. An array reference with at least two elements. The first element is
801 the value, the second element is its title.
802
803 =item 2. A scalar. The scalar is both the value and the title.
804
805 =item 3. A hash reference. In this case C<%options> must contain
806 I<value> and I<title> keys that name the keys in the element to use
807 for the value and title respectively.
808
809 =item 4. A blessed reference. In this case C<%options> must contain
810 I<value> and I<title> keys that name functions called on the blessed
811 reference whose return values are used as the value and title
812 respectively.
813
814 =back
815
816 For cases 3 and 4 C<$options{value}> defaults to C<id> and
817 C<$options{title}> defaults to C<$options{value}>.
818
819 In addition to pure keys/method you can also provide coderefs as I<value_sub>
820 and/or I<title_sub>. If present, these take precedence over keys or methods,
821 and are called with the element as first argument. It must return the value or
822 title.
823
824 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
825 precedence over each individual sub. It will only be called once for each
826 element and must return a list of value and title.
827
828 If the option C<with_empty> is set then an empty element (value
829 C<undef>) will be used as the first element. The title to display for
830 this element can be set with the option C<empty_title> and defaults to
831 an empty string.
832
833 The option C<default> can be either a scalar or an array reference
834 containing the values of the options which should be set to be
835 selected.
836
837 =item C<tab, description, target, %PARAMS>
838
839 Creates a tab for C<tabbed>. The description will be used as displayed name.
840 The target should be a block or template that can be processed. C<tab> supports
841 a C<method> parameter, which can override the process method to apply target.
842 C<method => 'raw'> will just include the given text as is. I was too lazy to
843 implement C<include> properly.
844
845 Also an C<if> attribute is supported, so that tabs can be suppressed based on
846 some occasion. In this case the supplied block won't even get processed, and
847 the resulting tab will get ignored by C<tabbed>:
848
849   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
850
851 =back
852
853 =head1 MODULE AUTHORS
854
855 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
856
857 L<http://linet-services.de>