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