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