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