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