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