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